@agentunion/fastaun-browser 0.2.18 → 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.
Files changed (90) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/_packed_docs/CHANGELOG.md +26 -0
  3. package/_packed_docs/agent.md/SCHEMA.md +173 -0
  4. package/_packed_docs/agent.md/examples/codeagent-claudecode.md +61 -0
  5. package/_packed_docs/agent.md/examples/human-developer.md +60 -0
  6. package/_packed_docs/agent.md/examples/openclaw-lobster.md +52 -0
  7. package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +43 -0
  8. package/_packed_docs/protocol/00-/346/200/273/350/247/210/344/270/216/345/210/206/345/261/202.md +205 -0
  9. 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
  10. 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
  11. 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
  12. package/_packed_docs/protocol/03-Gateway-/350/277/236/346/216/245/346/250/241/345/274/217.md +262 -0
  13. package/_packed_docs/protocol/04-Peer-/345/255/220/345/215/217/350/256/256.md +180 -0
  14. package/_packed_docs/protocol/05-Relay-/345/255/220/345/215/217/350/256/256.md +164 -0
  15. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1135 -0
  16. 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
  17. package/_packed_docs/protocol/08-AUN-E2EE-Group.md +900 -0
  18. package/_packed_docs/protocol/08-AUN-E2EE.md +413 -0
  19. package/_packed_docs/protocol/09-/345/256/211/345/205/250/350/200/203/350/231/221.md +316 -0
  20. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +804 -0
  21. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +271 -0
  22. package/_packed_docs/protocol/12-Stream-/345/255/220/345/215/217/350/256/256.md +329 -0
  23. package/_packed_docs/protocol/13-Agent/350/241/214/344/270/272/350/247/204/350/214/203.md +141 -0
  24. 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
  25. package/_packed_docs/protocol/README.md +71 -0
  26. package/_packed_docs/protocol/agent.md/SCHEMA.md +118 -0
  27. package/_packed_docs/protocol/agent.md/examples/codeagent-claudecode.md +61 -0
  28. package/_packed_docs/protocol/agent.md/examples/human-developer.md +60 -0
  29. package/_packed_docs/protocol/agent.md/examples/openclaw-lobster.md +52 -0
  30. package/_packed_docs/protocol/aun-docs-guide.md +49 -0
  31. package/_packed_docs/protocol/index.md +114 -0
  32. 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
  33. 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
  34. package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +337 -0
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +223 -0
  48. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +354 -0
  49. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +172 -0
  50. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +373 -0
  51. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +611 -0
  52. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +1152 -0
  53. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +150 -0
  54. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +89 -0
  55. package/_packed_docs/sdk/09-custody-api-manual.md +445 -0
  56. package/_packed_docs/sdk/09-group-rpc-manual.md +1895 -0
  57. package/_packed_docs/sdk/09-message-rpc-manual.md +597 -0
  58. package/_packed_docs/sdk/09-meta-rpc-manual.md +142 -0
  59. package/_packed_docs/sdk/09-payload-reference.md +702 -0
  60. package/_packed_docs/sdk/09-storage-rpc-manual.md +408 -0
  61. package/_packed_docs/sdk/09-stream-rpc-manual.md +275 -0
  62. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +72 -0
  63. package/_packed_docs/sdk/INDEX.md +131 -0
  64. package/_packed_docs/sdk/README.md +307 -0
  65. package/dist/auth.d.ts +11 -1
  66. package/dist/auth.d.ts.map +1 -1
  67. package/dist/auth.js +92 -17
  68. package/dist/auth.js.map +1 -1
  69. package/dist/client.d.ts +51 -9
  70. package/dist/client.d.ts.map +1 -1
  71. package/dist/client.js +394 -122
  72. package/dist/client.js.map +1 -1
  73. package/dist/e2ee.d.ts.map +1 -1
  74. package/dist/e2ee.js +20 -0
  75. package/dist/e2ee.js.map +1 -1
  76. package/dist/keystore/index.d.ts +11 -0
  77. package/dist/keystore/index.d.ts.map +1 -1
  78. package/dist/keystore/indexeddb.d.ts +35 -0
  79. package/dist/keystore/indexeddb.d.ts.map +1 -1
  80. package/dist/keystore/indexeddb.js +91 -0
  81. package/dist/keystore/indexeddb.js.map +1 -1
  82. package/dist/namespaces/auth.d.ts +10 -3
  83. package/dist/namespaces/auth.d.ts.map +1 -1
  84. package/dist/namespaces/auth.js +94 -15
  85. package/dist/namespaces/auth.js.map +1 -1
  86. package/dist/transport.d.ts +9 -1
  87. package/dist/transport.d.ts.map +1 -1
  88. package/dist/transport.js +24 -0
  89. package/dist/transport.js.map +1 -1
  90. package/package.json +4 -1
@@ -0,0 +1,267 @@
1
+ # 附录 L:E2EE 实现指南(非规范性)
2
+
3
+ > **本文档为非规范性内容**:提供端到端加密的实现建议、交互流程、代码示例和安全考虑,不是协议强制要求。
4
+ > 规范性定义见 [08-AUN-E2EE.md](08-AUN-E2EE.md)。
5
+
6
+ ## L.0 加密模式概述
7
+
8
+ AUN-E2EE 支持两种加密模式,SDK 实现应同时支持。
9
+
10
+ ### L.0.1 模式 1:prekey_ecdh_v2(优先)
11
+
12
+ **机制**:发送方获取接收方预上传的 prekey,生成临时 ECDH 密钥对,通过四路 ECDH(ephemeral × prekey + ephemeral × identity + sender_identity × prekey + sender_identity × identity)+ HKDF 派生消息密钥。
13
+
14
+ **优势**:
15
+ - 前向安全(临时密钥用完即丢)
16
+ - 一消息一密钥(每条消息独立临时密钥对)
17
+ - 四路 ECDH 绑定双方身份,防止 prekey 替换攻击和中间人攻击
18
+ - 支持接收方离线
19
+ - 无需在线协商
20
+
21
+ **限制**:
22
+ - 需要接收方预先上传 prekey
23
+
24
+ ### L.0.2 模式 2:long_term_key(降级)
25
+
26
+ **机制**:使用接收方长期公钥(从 X.509 证书获取),生成临时 ECDH 密钥对,通过双 DH(ephemeral × recipient_identity + sender_identity × recipient_identity)+ HKDF 派生消息密钥。
27
+
28
+ **优势**:
29
+ - 支持接收方离线且无 prekey 时发送加密消息
30
+ - 一消息一密钥(每条消息独立临时密钥对)
31
+ - 双 DH 绑定双方身份
32
+
33
+ **限制**:
34
+ - 不提供严格意义上的前向安全性
35
+ - 长期私钥泄露会导致历史消息被解密
36
+ - Python SDK 默认 `require_forward_secrecy=true`,会拒绝此模式;需显式配置才允许降级
37
+
38
+ ### L.0.3 模式选择策略
39
+
40
+ SDK 按以下优先级自动选择:
41
+
42
+ 1. **优先**:prekey_ecdh_v2(服务端有接收方 prekey)
43
+ 2. **降级**:long_term_key(无 prekey 时)
44
+
45
+ ---
46
+
47
+ ## L.1 交互流程
48
+
49
+ ### L.1.1 prekey_ecdh_v2 时序图
50
+
51
+ ```mermaid
52
+ sequenceDiagram
53
+ participant Receiver
54
+ participant Gateway
55
+ participant Sender
56
+
57
+ Note over Receiver: 上线时自动上传 prekey
58
+ Receiver->>Gateway: message.e2ee.put_prekey {prekey_id, public_key, signature, created_at}
59
+
60
+ Note over Sender: 发送加密消息
61
+ Sender->>Gateway: message.e2ee.get_prekey {aid: receiver}
62
+ Gateway-->>Sender: {prekey_id, public_key, signature, created_at}
63
+
64
+ Note over Sender: 验证 prekey 签名(用 receiver 证书)
65
+ Note over Sender: 生成临时 ECDH 密钥对
66
+ Note over Sender: 四路 ECDH:dh1 = ECDH(ephemeral, prekey), dh2 = ECDH(ephemeral, identity), dh3 = ECDH(sender_identity, prekey), dh4 = ECDH(sender_identity, identity)
67
+ Note over Sender: HKDF(dh1||dh2||dh3||dh4, "aun-prekey-v2:{prekey_id}") → message_key
68
+ Note over Sender: AES-256-GCM(message_key, plaintext, AAD) → ciphertext
69
+
70
+ Sender->>Gateway: message.send {encrypted: true, payload: envelope}
71
+ Gateway->>Receiver: 推送或 pull
72
+
73
+ Note over Receiver: 四路 ECDH(prekey_private + identity_private, ephemeral_public + sender_identity_public) → message_key → 解密
74
+ ```
75
+
76
+ ### L.1.2 long_term_key 时序图
77
+
78
+ ```mermaid
79
+ sequenceDiagram
80
+ participant Sender
81
+ participant Gateway
82
+ participant Receiver
83
+
84
+ Note over Sender: 获取 receiver 证书(HTTP /pki/cert/{aid})
85
+ Sender->>Gateway: GET /pki/cert/{receiver_aid}
86
+ Gateway-->>Sender: cert_pem
87
+
88
+ Note over Sender: 从证书提取长期公钥
89
+ Note over Sender: 生成临时 ECDH 密钥对
90
+ Note over Sender: DH1 = ECDH(ephemeral, recipient_identity)
91
+ Note over Sender: DH2 = ECDH(sender_identity, recipient_identity)
92
+ Note over Sender: HKDF(DH1 || DH2, info="aun-longterm-v2") → message_key
93
+ Note over Sender: AES-256-GCM(message_key, plaintext, AAD) → ciphertext
94
+ Note over Sender: ECDSA 签名(ciphertext + tag + aad_bytes) → sender_signature
95
+
96
+ Sender->>Gateway: message.send {encrypted: true, payload: envelope}
97
+ Gateway->>Receiver: 推送或 pull
98
+
99
+ Note over Receiver: 验证 sender_signature(用发送方证书)
100
+ Note over Receiver: DH1 = ECDH(identity_private, ephemeral)
101
+ Note over Receiver: DH2 = ECDH(identity_private, sender_identity)
102
+ Note over Receiver: HKDF → message_key → AES-GCM 解密 → plaintext
103
+ ```
104
+
105
+ ---
106
+
107
+ ## L.2 SDK 实现参考(Python)
108
+
109
+ ### L.2.1 通过 AUNClient 发送加密消息(推荐)
110
+
111
+ ```python
112
+ # SDK 自动获取证书和 prekey,自动选择加密模式
113
+ await client.call("message.send", {
114
+ "to": "bob.agentid.pub",
115
+ "payload": {"type": "text", "text": "秘密消息"},
116
+ "encrypt": True,
117
+ })
118
+ ```
119
+
120
+ ### L.2.2 通过 AUNClient 接收加密消息
121
+
122
+ ```python
123
+ # 推送自动解密
124
+ client.on("message.received", lambda msg: print(msg["payload"]))
125
+
126
+ # Pull 自动解密
127
+ result = await client.call("message.pull", {"after_seq": 0, "limit": 50})
128
+ for msg in result["messages"]:
129
+ if msg.get("encrypted"):
130
+ print(f"加密模式: {msg['e2ee']['encryption_mode']}")
131
+ print(f"内容: {msg['payload']}")
132
+ ```
133
+
134
+ ### L.2.3 裸 WebSocket 开发者
135
+
136
+ ```python
137
+ from aun_core.e2ee import E2EEManager
138
+ from aun_core.keystore.file import FileKeyStore
139
+
140
+ # 1. 实例化(纯工具类,无 I/O 依赖)
141
+ e2ee = E2EEManager(
142
+ identity_fn=lambda: my_identity,
143
+ keystore=FileKeyStore("~/.aun/myapp"),
144
+ )
145
+
146
+ # 2. 获取对方证书和 prekey(调用方自行实现 I/O)
147
+ peer_cert_pem = await fetch_cert("bob.agentid.pub")
148
+ prekey = await rpc_call("message.e2ee.get_prekey", {"aid": "bob.agentid.pub"})
149
+ prekey_data = prekey.get("prekey") if prekey.get("found") else None
150
+
151
+ # 3. 加密(prekey 传入后自动缓存,后续可传 None 复用缓存)
152
+ envelope, ok = e2ee.encrypt_message(
153
+ to_aid="bob.agentid.pub",
154
+ payload={"type": "text", "text": "秘密消息"},
155
+ peer_cert_pem=peer_cert_pem,
156
+ prekey=prekey_data,
157
+ )
158
+
159
+ # 4. 通过 WebSocket 发送
160
+ aad = envelope["aad"]
161
+ await rpc_call("message.send", {
162
+ "to": "bob.agentid.pub",
163
+ "payload": envelope,
164
+ "type": "e2ee.encrypted",
165
+ "encrypted": True,
166
+ "message_id": aad["message_id"],
167
+ "timestamp": aad["timestamp"],
168
+ "delivery_mode": {"mode": "fanout"},
169
+ })
170
+
171
+ # 5. 解密(内置本地防重放)
172
+ decrypted = e2ee.decrypt_message(raw_message)
173
+ ```
174
+
175
+ ---
176
+
177
+ ## L.3 Prekey 管理
178
+
179
+ ### L.3.1 生成与上传
180
+
181
+ ```python
182
+ # E2EEManager 只生成材料,不做 RPC
183
+ material = e2ee.generate_prekey()
184
+ # 返回: {"prekey_id": "uuid", "public_key": "base64", "signature": "base64"}
185
+
186
+ # 调用方自行上传
187
+ await rpc_call("message.e2ee.put_prekey", material)
188
+ ```
189
+
190
+ AUNClient 连接时自动上传 prekey,并定时轮换(默认每小时)。
191
+
192
+ ### L.3.2 Prekey 签名
193
+
194
+ 签名格式:`ECDSA(identity_private_key, "prekey_id|public_key_b64|created_at")`
195
+
196
+ 接收方用身份私钥签名 prekey(绑定时间戳防止旧 prekey 重放),发送方用接收方证书验证签名,防止服务端替换假 prekey。
197
+
198
+ ### L.3.3 旧 Prekey 私钥保留
199
+
200
+ 旧 prekey 私钥本地保留 7 天(`PREKEY_RETENTION_SECONDS`),确保轮换后在途消息仍可解密。
201
+
202
+ ### L.3.4 Prekey 缓存
203
+
204
+ E2EEManager 内置 prekey 缓存(默认 TTL 1 小时):
205
+
206
+ ```python
207
+ e2ee.cache_prekey("bob.agentid.pub", prekey_dict) # 手动缓存
208
+ cached = e2ee.get_cached_prekey("bob.agentid.pub") # 查询缓存
209
+ e2ee.invalidate_prekey_cache("bob.agentid.pub") # 使缓存失效
210
+ ```
211
+
212
+ `encrypt_message` / `encrypt_outbound` 传入 prekey 时自动缓存,传入 None 时自动查缓存。
213
+
214
+ ---
215
+
216
+ ## L.4 防重放
217
+
218
+ ### L.4.1 本地防重放(E2EEManager 内置)
219
+
220
+ - 维护 `seen_messages` 集合,以 `{sender_aid}:{message_id}` 为 key
221
+ - 同一 key 的消息返回 `None`(拒绝解密)
222
+ - 集合上限 5000 条,超出时淘汰最旧的 20%
223
+
224
+ ### L.4.2 服务端防重放(AUNClient 可选增强)
225
+
226
+ 通过 `server_replay_guard` 配置项开启(默认关闭):
227
+
228
+ ```python
229
+ client = AUNClient({"server_replay_guard": True})
230
+ ```
231
+
232
+ 调用 `message.e2ee.record_replay_guard` RPC,跨进程/跨重启持久化防重放。
233
+
234
+ ---
235
+
236
+ ## L.5 防篡改
237
+
238
+ AES-GCM + AAD 验证保证消息完整性。AAD 包含以下字段,按 Canonical JSON for AAD 规范序列化(递归键排序 + 紧凑格式 + UTF-8 直接输出,详见 P2P E2EE §8.3):
239
+
240
+ | 字段 | 说明 |
241
+ |------|------|
242
+ | `from` | 发送方 AID |
243
+ | `to` | 接收方 AID |
244
+ | `message_id` | 消息唯一标识 |
245
+ | `timestamp` | 发送时间戳(ms) |
246
+ | `encryption_mode` | 加密模式 |
247
+ | `suite` | 算法套件 |
248
+ | `ephemeral_public_key` | 发送方临时公钥 |
249
+ | `recipient_cert_fingerprint` | 接收方证书公钥指纹 |
250
+
251
+ 任何字段被篡改都会导致 AEAD tag 校验失败,解密报错。
252
+
253
+ ---
254
+
255
+ ## L.6 安全建议
256
+
257
+ 1. **不静默降级**:加密失败时不应静默发送明文,应由上层应用显式决定
258
+ 2. **临时密钥**:每条消息使用独立的临时 ECDH 密钥对,用完即丢
259
+ 3. **私钥保护**:Prekey 私钥通过 FileKeyStore 加密存储(Windows DPAPI / SecretStore)
260
+ 4. **证书验证**:发送方必须验证 prekey 签名,防止中间人替换
261
+ 5. **Prekey 轮换**:建议每小时轮换一次 prekey,旧私钥保留 7 天
262
+
263
+ ---
264
+
265
+ ## L.7 兼容性说明
266
+
267
+ - 发送端 **MUST** 使用 `prekey_ecdh_v2`(四路 ECDH)模式发送新消息
@@ -0,0 +1,367 @@
1
+ # 附录 M:JWT 认证实现指南(非规范性)
2
+
3
+ > **本文档为非规范性内容**:提供 JWT Token 认证的实现建议、代码示例和安全考虑,不是协议强制要求。
4
+
5
+ ## M.1 Token 签发实现
6
+
7
+ ### M.1.1 签发流程
8
+
9
+ **Auth 服务端实现**:
10
+
11
+ ```
12
+ 1. 客户端完成两阶段认证
13
+
14
+ 2. Auth 服务验证签名和证书
15
+ - 从 cert 中提取公钥
16
+ - 验证 signature = sign(privateKey, nonce)
17
+ - 验证 cert 由 CA 签发
18
+ - 验证 cert 未过期
19
+ - 验证 cert.CN == aid
20
+
21
+ 3. Auth 服务生成 JWT token
22
+ - Header: {"alg": "ES256", "typ": "JWT"} (P-256 Auth 服务) 或 {"alg": "ES384", "typ": "JWT"} (P-384 Auth 服务)
23
+ - Payload: {
24
+ "aid": "alice.aid.pub",
25
+ "iat": 1709712000, // 签发时间
26
+ "exp": 1709798400, // 过期时间(24小时后)
27
+ "iss": "auth.aid.pub", // 签发者(Auth 服务的 AID)
28
+ "sub": "alice.aid.pub", // 主体(用户 AID)
29
+ "aud": "aun" // 受众(固定值 "aun",标识 token 用途)
30
+ }
31
+ - Signature: ECDSA-SHA256(Header + Payload, Auth_PrivateKey) 或 ECDSA-SHA384
32
+
33
+ 4. Auth 服务返回 token
34
+ → {token: "eyJhbGc...", expires_in: 3600}
35
+ ```
36
+
37
+ ### M.1.2 JWT Token 结构
38
+
39
+ ```
40
+ eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJhaWQiOiJhbGljZS5haWQucHViIiwiaWF0IjoxNzA5NzEyMDAwLCJleHAiOjE3MDk3OTg0MDAsImlzcyI6ImFwLmFpZC5wdWIiLCJzdWIiOiJhbGljZS5haWQucHViIn0.signature_bytes
41
+ │ │ │
42
+ │ Header (Base64) │ Payload (Base64) │ Signature
43
+ ```
44
+
45
+ **签名算法**:
46
+ - 算法:ES256(ECDSA-SHA256)或 ES384(ECDSA-SHA384)
47
+ - Auth 服务密钥为 P-256 时使用 ES256,P-384 时使用 ES384
48
+ - 使用 Auth 服务私钥进行 ECDSA 签名(非对称签名)
49
+ - 所有服务持有 Auth 服务公钥证书,可独立验证 token(无需共享密钥)
50
+
51
+ ## M.2 Token 验证实现
52
+
53
+ ### M.2.1 验证流程
54
+
55
+ **所有 AUN 服务端实现**:
56
+
57
+ ```
58
+ 1. 服务收到请求(携带 JWT token)
59
+
60
+ 2. 提取 token
61
+ - WebSocket: 从 `auth.connect.params.auth.token` 中获取
62
+ - 连接状态:token 验证后存储在连接上下文
63
+
64
+ 3. 解析 token
65
+ - Base64 解码 Header 和 Payload
66
+ - 提取签名部分
67
+
68
+ 4. 验证签名
69
+ - 使用 Auth 服务的公钥证书
70
+ - 验证 ECDSA 签名
71
+ - 算法:ES256(ECDSA-SHA256)或 ES384(ECDSA-SHA384)
72
+
73
+ 5. 验证 Payload
74
+ - 检查 exp(过期时间):exp > now
75
+ - 检查 iss(签发者):iss == "auth.aid.pub"
76
+ - 检查 aud(受众):aud == "aun"
77
+ - 检查 aid(用户身份):aid 格式正确
78
+
79
+ 6. 提取用户信息
80
+ - aid: 用户的 Agent Identifier
81
+ - aud: 验证 aud == "aun",确认 token 用途
82
+ - 用于标识请求来源身份(身份认证)
83
+ - **注意**:JWT 仅提供身份认证,不包含授权信息。资源访问控制由各 AUN 服务根据业务逻辑自行实现
84
+ ```
85
+
86
+ ### M.2.2 Go 实现示例
87
+
88
+ ```go
89
+ import (
90
+ "github.com/golang-jwt/jwt/v5"
91
+ )
92
+
93
+ // Auth 服务的公钥证书(ECDSA 非对称签名,所有服务持有公钥)
94
+ var authPublicKey *ecdsa.PublicKey
95
+
96
+ func verifyToken(tokenString string) (*Claims, error) {
97
+ token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
98
+ // 验证签名算法(ES256 或 ES384)
99
+ if _, ok := token.Method.(*jwt.SigningMethodECDSA); !ok {
100
+ return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
101
+ }
102
+ return authPublicKey, nil
103
+ })
104
+
105
+ if err != nil {
106
+ return nil, err
107
+ }
108
+
109
+ if claims, ok := token.Claims.(*Claims); ok && token.Valid {
110
+ return claims, nil
111
+ }
112
+
113
+ return nil, fmt.Errorf("invalid token")
114
+ }
115
+
116
+ type Claims struct {
117
+ AID string `json:"aid"`
118
+ jwt.RegisteredClaims
119
+ }
120
+ ```
121
+
122
+ ## M.3 客户端 Token 使用
123
+
124
+ ### M.3.1 客户端签名实现
125
+
126
+ 在 `auth.aid_login2` 阶段,客户端需要对 nonce 进行签名:
127
+
128
+ **签名内容**:
129
+ ```
130
+ message = nonce + ":" + client_time
131
+ signature = ECDSA_sign(private_key, SHA256(message))
132
+ ```
133
+
134
+ **JavaScript 实现示例**:
135
+ ```javascript
136
+ // 客户端签名
137
+ const nonce = "server_challenge_nonce";
138
+ const client_time = Math.floor(Date.now() / 1000);
139
+ const message = `${nonce}:${client_time}`;
140
+ const signature = await crypto.subtle.sign(
141
+ { name: "ECDSA", hash: "SHA-256" },
142
+ privateKey,
143
+ new TextEncoder().encode(message)
144
+ );
145
+
146
+ // 发送 login_aid2 请求
147
+ const response = await rpc.call('auth.aid_login2', {
148
+ request_id: requestId,
149
+ nonce: nonce,
150
+ client_time: client_time,
151
+ signature: arrayBufferToBase64(signature),
152
+ cert: certPem
153
+ });
154
+
155
+ // 获取 token
156
+ const token = response.token;
157
+ ```
158
+
159
+ ### M.3.2 Token 使用示例
160
+
161
+ Token 统一通过 `auth.connect` 消息传递。每次 WebSocket 连接(包括重连)都必须在收到 Gateway `challenge` 后调用 `auth.connect`:
162
+
163
+ ```javascript
164
+ function sendAuthConnect(ws, nonce, token) {
165
+ ws.send(JSON.stringify({
166
+ jsonrpc: '2.0',
167
+ id: 1,
168
+ method: 'auth.connect',
169
+ params: {
170
+ nonce,
171
+ auth: { method: 'kite_token', token },
172
+ protocol: { min: '1.0', max: '1.0' },
173
+ device: { id: 'dev-001', type: 'browser' },
174
+ client: { name: 'MyApp', version: '1.0.0' },
175
+ capabilities: { e2ee: true, group_e2ee: true }
176
+ }
177
+ }));
178
+ }
179
+
180
+ // 首次登录:先调用 auth.* 获取 token,再 auth.connect
181
+ const ws = new WebSocket('wss://gateway.example.com/aun');
182
+ ws.onmessage = async (event) => {
183
+ const msg = JSON.parse(event.data);
184
+ if (msg.method !== 'challenge') return;
185
+
186
+ // 1. 在 auth.connect 之前调用 auth.* 获取 token
187
+ const token = await loginFlow(ws); // login_aid1 + login_aid2
188
+
189
+ // 2. 用 token 调用 auth.connect 完成会话初始化
190
+ sendAuthConnect(ws, msg.params.nonce, token);
191
+ };
192
+
193
+ // 重连:直接用已有 token 调用 auth.connect
194
+ const ws2 = new WebSocket('wss://gateway.example.com/aun');
195
+ ws2.onmessage = (event) => {
196
+ const msg = JSON.parse(event.data);
197
+ if (msg.method === 'challenge') {
198
+ sendAuthConnect(ws2, msg.params.nonce, saved_jwt_token);
199
+ }
200
+ };
201
+ ```
202
+
203
+ **优势**:
204
+ - Token 不出现在 URL、服务器日志和浏览器历史中
205
+ - 统一的传递方式,所有客户端实现一致
206
+ - 与 JSON-RPC 2.0 协议自然融合
207
+
208
+ ## M.4 安全架构
209
+
210
+ ### M.4.1 单一信任根
211
+
212
+ ```
213
+ ┌─────────────────────────────────────────────────┐
214
+ │ 单一信任根架构 │
215
+ ├─────────────────────────────────────────────────┤
216
+ │ │
217
+ │ Auth 服务 │
218
+ │ ├─ 持有私钥(唯一能签发 token) │
219
+ │ └─ 签发 JWT token │
220
+ │ │
221
+ │ 所有 AUN 服务 │
222
+ │ ├─ HB (Heartbeat) │
223
+ │ ├─ MSG (Message) │
224
+ │ ├─ Storage │
225
+ │ ├─ Group │
226
+ │ └─ 都持有 Auth 服务的公钥证书 │
227
+ │ └─ 可以验证 token(ECDSA 非对称签名) │
228
+ │ │
229
+ │ Gateway │
230
+ │ ├─ 只能转发认证请求 │
231
+ │ ├─ 无法伪造 Auth 服务的签名 │
232
+ │ └─ 恶意 Gateway 签发的假 token 会被拒绝 │
233
+ │ │
234
+ └─────────────────────────────────────────────────┘
235
+ ```
236
+
237
+ ### M.4.2 防止 Gateway 作恶
238
+
239
+ 1. **无法伪造 token**:
240
+ - Gateway 没有 Auth 服务的私钥
241
+ - 无法生成有效的 ECDSA 签名
242
+ - 所有服务都会拒绝无效签名的 token
243
+
244
+ 2. **无法冒充其他用户**:
245
+ - Token 中的 `aid` 字段标识用户身份
246
+ - 即使恶意 Gateway 用自己的 AID 获取 token
247
+ - 也无法访问其他用户的资源(token 中的 aid 与资源归属不匹配)
248
+
249
+ 3. **私钥不经过 Gateway**:
250
+ - 客户端本地签名 nonce
251
+ - Gateway 只转发签名,看不到私钥
252
+ - 无法伪造用户签名
253
+
254
+ ## M.5 Token 生命周期管理
255
+
256
+ ### M.5.1 有效期与刷新
257
+
258
+ **有效期**:
259
+ - 推荐:1-24 小时
260
+ - 具体由 Auth 服务决定
261
+
262
+ **过期处理**:
263
+ - 客户端需要重新认证
264
+ - 或使用 `auth.refresh_token` 刷新
265
+
266
+ **刷新机制**:
267
+
268
+ AUN 协议定义了简化的单 token 刷新模型(参见主规范 8.3.4 节):
269
+ - 客户端在已认证连接上调用 `auth.refresh_token`(空参数)
270
+ - Auth 服务返回新的 JWT token
271
+ - 旧 token 在过期前仍然有效
272
+
273
+ **可选增强:双 Token 模式**
274
+
275
+ 实现方可以选择使用更安全的双 token 模式:
276
+ ```
277
+ 短期 access_token(1小时)+ 长期 refresh_token(7天)
278
+ access_token 过期后,用 refresh_token 换取新的 access_token
279
+ ```
280
+
281
+ 这种模式的优势:
282
+ - access_token 有效期短,降低泄露风险
283
+ - refresh_token 只在刷新时使用,减少暴露
284
+ - 可以实现更细粒度的撤销控制
285
+
286
+ **注意**:双 token 模式需要扩展 `auth.refresh_token` 的参数和响应结构,不在核心协议规范中定义。
287
+
288
+ **JWT 与证书绑定**:
289
+ - JWT 的有效期不得超过签发该 JWT 的 Auth 服务证书的有效期
290
+ - 证书过期后所有由该证书签发的 JWT 自动失效
291
+
292
+ **刷新限制**:
293
+ - 刷新链总时长不超过 30 天,或最多刷新 720 次
294
+ - 达到限制后必须重新用证书签名登录
295
+
296
+ ## M.6 证书轮换与双证书过渡
297
+
298
+ ### M.6.1 轮换通用原则
299
+
300
+ 整个证书层级(Root CA → Issuer CA → Auth 服务/Agent)都存在轮换需求,轮换期间必须保证新旧双证书同时有效:
301
+
302
+ ```
303
+ 证书轮换通用原则:
304
+
305
+ 1. 新证书签发时,旧证书仍在有效期内
306
+ 2. 进入双证书过渡期:新旧证书并存于证书库
307
+ 3. 过渡期内,两张证书都可用于验证
308
+ 4. 过渡期结束后(旧证书签发的所有下级证书/JWT 均已过期),旧证书退役
309
+ ```
310
+
311
+ ### M.6.2 各层级轮换要点
312
+
313
+ | 层级 | 典型有效期 | 过渡期 | 影响范围 |
314
+ |------|-----------|--------|---------|
315
+ | Root CA | 20-30 年 | 数年 | 全局信任锚,所有客户端证书库需更新 |
316
+ | Issuer CA | 10-15 年 | 1-2 年 | 该 Issuer 下所有 Agent |
317
+ | Auth 服务证书 | 1-2 年 | ≤ 旧 token 最大剩余有效期 | 已签发的 JWT |
318
+
319
+ ### M.6.3 证书库要求
320
+
321
+ - `auth.download_cert` 返回的证书库必须包含过渡期内的新旧两张证书
322
+ - 客户端验证证书链时,按证书序列号匹配,新旧证书均可构成有效链
323
+ - Root CA 轮换时,客户端受信列表需同时包含新旧两个根证书,直到旧根签发的所有下级证书全部过期
324
+
325
+ ### M.6.4 Auth 服务证书轮换与 JWT 有效性
326
+
327
+ Auth 服务证书轮换直接影响 JWT 验证,需特别处理:
328
+
329
+ - 新 JWT 用新证书私钥签发
330
+ - 旧 JWT 仍可用旧证书公钥验证
331
+ - JWT Header 中通过 `kid`(Key ID)标识签发证书:`{"alg": "ES256", "kid": "auth-cert-sn-002"}`
332
+ - 验证端按 `kid` 匹配证书公钥
333
+ - 过渡期 = 旧 token 最大剩余有效期(通常 ≤ 24 小时)
334
+ - 过渡期结束后,旧 Auth 服务证书退役
335
+ - 客户端无感知,无需重新登录
336
+
337
+ ## M.7 审计与监控
338
+
339
+ ### M.7.1 日志记录
340
+
341
+ **Auth 服务**:
342
+ - 记录所有 token 签发日志
343
+ - 包含:aid、签发时间、过期时间、客户端信息
344
+
345
+ **各 AUN 服务**:
346
+ - 记录 token 验证失败日志
347
+ - 包含:token 内容、失败原因、请求来源
348
+
349
+ ### M.7.2 异常检测
350
+
351
+ - 追踪异常 token 使用模式
352
+ - 检测重放攻击
353
+ - 检测伪造 token 尝试
354
+
355
+ ### M.7.3 Token 撤销
356
+
357
+ **黑名单机制**:
358
+ - 维护已撤销 token 的黑名单
359
+ - 验证时检查 token 是否在黑名单中
360
+ - 黑名单条目在 token 过期后自动清理
361
+
362
+ **撤销场景**:
363
+ - 用户主动登出
364
+ - 检测到账户异常
365
+ - 证书被吊销
366
+
367
+ ---