@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,150 @@
|
|
|
1
|
+
# AUN SDK Python - 错误处理
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## 错误类层级
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
AUNError
|
|
9
|
+
├── ConnectionError # 网络连接失败
|
|
10
|
+
├── TimeoutError # 操作超时
|
|
11
|
+
├── AuthError # 认证失败(code: 4001/4010/-32001/-32003)
|
|
12
|
+
│ └── CertificateRevokedError # 证书已吊销(code: -32050)
|
|
13
|
+
├── PermissionError # 权限不足(code: 4030/403/-32004)
|
|
14
|
+
├── ValidationError # 参数校验失败(code: 4000/-32600/-32601/-32602)
|
|
15
|
+
├── NotFoundError # 资源不存在(code: 4040/404/-32008)
|
|
16
|
+
├── RateLimitError # 请求限流(code: 4290/429/-32029),retryable=True
|
|
17
|
+
├── VersionConflictError # 版本冲突(code: -32009)
|
|
18
|
+
├── StateError # 非法状态操作
|
|
19
|
+
├── SerializationError # JSON 序列化失败
|
|
20
|
+
├── SessionError # 会话错误(code: -32010/-32011/-32013)
|
|
21
|
+
├── ClientSignatureError # 客户端签名验证失败(code: -32051)
|
|
22
|
+
├── GroupError
|
|
23
|
+
│ ├── GroupNotFoundError # code: -33001
|
|
24
|
+
│ └── GroupStateError # code: -33002/-33003
|
|
25
|
+
└── E2EEError
|
|
26
|
+
├── E2EEDecryptFailedError # P2P 消息解密失败
|
|
27
|
+
├── E2EEDegradedError # E2EE 降级为 long_term_key(无 prekey 可用)
|
|
28
|
+
├── E2EEGroupSecretMissingError # code: -32040,缺少群密钥
|
|
29
|
+
├── E2EEGroupEpochMismatchError # code: -32041,epoch 不匹配
|
|
30
|
+
├── E2EEGroupCommitmentInvalidError # code: -32042,成员承诺验证失败
|
|
31
|
+
├── E2EEGroupNotMemberError # code: -32043,请求者非群成员
|
|
32
|
+
└── E2EEGroupDecryptFailedError # code: -32044,群消息解密失败
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 错误属性
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from aun_core import AUNError
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
await client.call("method.name", {...})
|
|
42
|
+
except AUNError as e:
|
|
43
|
+
e.code # int,错误码
|
|
44
|
+
e.data # Any,附加数据
|
|
45
|
+
e.retryable # bool,是否可重试
|
|
46
|
+
e.trace_id # str | None,追踪 ID
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 错误码速查
|
|
50
|
+
|
|
51
|
+
| 范围 | 含义 | 对应异常 |
|
|
52
|
+
|------|------|----------|
|
|
53
|
+
| 4000 | 参数校验失败 | `ValidationError` |
|
|
54
|
+
| 4001 / 4010 | 认证失败 | `AuthError` |
|
|
55
|
+
| 4030 / 403 | 权限不足 | `PermissionError` |
|
|
56
|
+
| 4040 / 404 | 资源不存在 | `NotFoundError` |
|
|
57
|
+
| 4290 / 429 | 请求限流 | `RateLimitError` |
|
|
58
|
+
| -32001 / -32003 | 认证失败(协议级) | `AuthError` |
|
|
59
|
+
| -32004 | 权限不足(协议级) | `PermissionError` |
|
|
60
|
+
| -32008 | 资源不存在(协议级) | `NotFoundError` |
|
|
61
|
+
| -32009 | 版本冲突 | `VersionConflictError` |
|
|
62
|
+
| -32010 / -32011 / -32013 | 会话错误 | `SessionError` |
|
|
63
|
+
| -32029 | 请求限流(协议级) | `RateLimitError` |
|
|
64
|
+
| -32600 / -32601 / -32602 | JSON-RPC 参数错误 | `ValidationError` |
|
|
65
|
+
| -32040 | 缺少群密钥 | `E2EEGroupSecretMissingError` |
|
|
66
|
+
| -32041 | 群 epoch 不匹配 | `E2EEGroupEpochMismatchError` |
|
|
67
|
+
| -32042 | 成员承诺验证失败 | `E2EEGroupCommitmentInvalidError` |
|
|
68
|
+
| -32043 | 密钥请求者非群成员 | `E2EEGroupNotMemberError` |
|
|
69
|
+
| -32044 | 群消息解密失败 | `E2EEGroupDecryptFailedError` |
|
|
70
|
+
| -32050 | 证书已吊销 | `CertificateRevokedError` |
|
|
71
|
+
| -32051 | 客户端签名验证失败 | `ClientSignatureError` |
|
|
72
|
+
| -33001 | 群组不存在 | `GroupNotFoundError` |
|
|
73
|
+
| -33002 / -33003 | 群组状态异常 | `GroupStateError` |
|
|
74
|
+
| -33004 ~ -33009 | 群组通用错误 | `GroupError` |
|
|
75
|
+
|
|
76
|
+
## 重试策略
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from aun_core import AUNError, RateLimitError, AuthError
|
|
80
|
+
|
|
81
|
+
async def send_with_retry(client, params, max_retries=3):
|
|
82
|
+
for i in range(max_retries):
|
|
83
|
+
try:
|
|
84
|
+
return await client.call("message.send", params)
|
|
85
|
+
except RateLimitError:
|
|
86
|
+
await asyncio.sleep(2 ** i)
|
|
87
|
+
except AuthError:
|
|
88
|
+
raise # 认证错误不重试
|
|
89
|
+
except AUNError as e:
|
|
90
|
+
if not e.retryable or i == max_retries - 1:
|
|
91
|
+
raise
|
|
92
|
+
await asyncio.sleep(0.5)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 常见错误场景
|
|
96
|
+
|
|
97
|
+
### 未认证就发消息
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
# ❌ 错误:跳过认证直接操作
|
|
101
|
+
client = AUNClient()
|
|
102
|
+
await client.call("message.send", {...}) # → AuthError
|
|
103
|
+
|
|
104
|
+
# ✅ 正确:先认证再操作
|
|
105
|
+
auth = await client.auth.authenticate({"aid": MY_AID})
|
|
106
|
+
await client.connect(auth, {})
|
|
107
|
+
await client.call("message.send", {...})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### E2EE 解密失败
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
# E2EEDecryptFailedError — prekey 不匹配、AAD 篡改、密文损坏
|
|
114
|
+
# SDK 自动处理:解密失败的消息返回原始密文,不中断其他消息
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 群组 E2EE 错误
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
# E2EEGroupSecretMissingError — 发送加密群消息时本地无群密钥
|
|
121
|
+
# 常见于建群后 create_epoch 尚未完成、或通过邀请码入群后尚未恢复密钥
|
|
122
|
+
# SDK 会抛出此异常,调用方可捕获后等待密钥恢复完成再重试
|
|
123
|
+
# SDK 自动编排:建群后自动 create_epoch;缺密钥时自动发起恢复请求
|
|
124
|
+
|
|
125
|
+
# E2EEGroupDecryptFailedError — 群消息解密失败(密钥不匹配或密文损坏)
|
|
126
|
+
# 协议层定义的错误语义,当前 Python SDK 的部分路径表现为返回 None、拒绝状态或跳过自动解密,不一定抛出对应异常
|
|
127
|
+
|
|
128
|
+
# E2EEGroupEpochMismatchError — 收到的密钥分发 epoch 低于当前 epoch
|
|
129
|
+
# 协议层定义的错误语义,当前 Python SDK 的部分路径表现为返回 None、拒绝状态或跳过自动解密,不一定抛出对应异常
|
|
130
|
+
|
|
131
|
+
# E2EEGroupCommitmentInvalidError — 收到的密钥分发中成员承诺校验失败
|
|
132
|
+
# 协议层定义的错误语义,当前 Python SDK 的部分路径表现为返回 None、拒绝状态或跳过自动解密,不一定抛出对应异常
|
|
133
|
+
# 可能原因:中间人篡改、成员列表不一致
|
|
134
|
+
|
|
135
|
+
# E2EEGroupNotMemberError — 密钥恢复请求被拒,请求者不在群成员列表中
|
|
136
|
+
# 协议层定义的错误语义,当前 Python SDK 的部分路径表现为返回 None、拒绝状态或跳过自动解密,不一定抛出对应异常
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 连接状态错误
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
# ❌ 错误:断连后直接调用
|
|
143
|
+
await client.call("message.send", {...}) # → ConnectionError("client is not connected")
|
|
144
|
+
|
|
145
|
+
# ✅ 正确:重新认证并启用自动重连
|
|
146
|
+
auth = await client.auth.authenticate({"aid": MY_AID})
|
|
147
|
+
await client.connect(auth, {
|
|
148
|
+
"auto_reconnect": True,
|
|
149
|
+
})
|
|
150
|
+
```
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# AUN SDK Python - 最佳实践
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## 1. 幂等的连接初始化
|
|
6
|
+
|
|
7
|
+
每次启动时安全地确保已认证并连接,无论是首次还是重复运行:
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
async def ensure_connected(client: AUNClient, aid: str) -> str:
|
|
11
|
+
try:
|
|
12
|
+
await client.auth.create_aid({"aid": aid})
|
|
13
|
+
except Exception as e:
|
|
14
|
+
print(f"创建 AID 失败: {e}")
|
|
15
|
+
raise
|
|
16
|
+
auth = await client.auth.authenticate({"aid": aid})
|
|
17
|
+
await client.connect(auth, {"auto_reconnect": True})
|
|
18
|
+
return aid
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 2. 安全关闭多个客户端
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
async def close_all(*clients: AUNClient):
|
|
25
|
+
for c in clients:
|
|
26
|
+
try:
|
|
27
|
+
await c.close()
|
|
28
|
+
except Exception:
|
|
29
|
+
pass
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 3. E2EE 幂等运行
|
|
33
|
+
|
|
34
|
+
每次运行前清除旧 prekey 缓存并跳过历史消息,避免计数器冲突:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
# 清除旧 prekey 缓存
|
|
38
|
+
sender.e2ee.invalidate_prekey_cache(peer_aid=receiver_aid)
|
|
39
|
+
receiver.e2ee.invalidate_prekey_cache(peer_aid=sender_aid)
|
|
40
|
+
|
|
41
|
+
# 跳过旧消息(获取当前最大 seq 作为 cursor 起点)
|
|
42
|
+
r = await receiver.call("message.pull", {"after_seq": 0, "limit": 1})
|
|
43
|
+
recv_cursor = r.get("latest_seq", 0)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 4. 多 AID 管理
|
|
47
|
+
|
|
48
|
+
一个 `aun_path` 可管理多个 AID,每个 AID 数据隔离在 `{aun_path}/AIDs/{aid}/` 下:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
# 推荐:用应用名作为 aun_path,多个 AID 共存于同一目录
|
|
52
|
+
client = AUNClient({"aun_path": "~/.aun/myapp"})
|
|
53
|
+
|
|
54
|
+
# 创建不同的 AID,各自数据自动隔离
|
|
55
|
+
await client.auth.create_aid({"aid": "alice.agentid.pub"})
|
|
56
|
+
await client.auth.create_aid({"aid": "bob.agentid.pub"})
|
|
57
|
+
# 数据分别在 ~/.aun/myapp/AIDs/alice.agentid.pub/ 和 bob.agentid.pub/ 下
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
> **注意**:不要用 AID 名称作为 `aun_path`(如 `~/.aun/{aid}`),否则会产生冗余嵌套。
|
|
61
|
+
|
|
62
|
+
## 5. 环境变量驱动配置
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import os
|
|
66
|
+
from pathlib import Path
|
|
67
|
+
|
|
68
|
+
DATA_ROOT = os.environ.get("AUN_DATA_ROOT", str(Path.home() / ".aun"))
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 6. 资源清理
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
async def main():
|
|
75
|
+
client = AUNClient()
|
|
76
|
+
try:
|
|
77
|
+
await ensure_connected(client, aid)
|
|
78
|
+
# ... 业务逻辑
|
|
79
|
+
finally:
|
|
80
|
+
await client.close()
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 参考
|
|
84
|
+
|
|
85
|
+
- 协议文档:`../src/aun_core/docs/protocol/`
|
|
86
|
+
- SDK 架构文档:`../src/aun_core/docs/skill/sdk-core/`
|
|
87
|
+
- RPC 方法手册:`../src/aun_core/docs/skill/rpc-manual/`
|
|
88
|
+
- 可运行示例:`../src/aun_core/docs/skill/examples/`
|
|
89
|
+
- GitHub:https://github.com/ModelUnion/aun-sdk-core
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
# AID 托管(Custody)HTTP API 手册
|
|
2
|
+
|
|
3
|
+
AID Custody 是 AUN AP 可选提供的 AID 备份、恢复与跨设备复制服务,不属于 AUN 核心协议强制能力。当前阶段定义两条主流程:通过手机号和验证码上传、下载 AID 证书以及客户端加密后的私钥文件;以及旧设备通过 AID token 授权,新设备凭一次性复制码领取 OTP 加密材料的跨设备复制流程。
|
|
4
|
+
|
|
5
|
+
用户也可以部署自己的 AID 托管服务。只要服务地址发现格式和本手册定义的 HTTP 接口、请求响应语义保持一致,SDK 即可通过 `client.custody.set_url(...)` 或 well-known 自动发现接入自部署服务。
|
|
6
|
+
|
|
7
|
+
核心原则:
|
|
8
|
+
|
|
9
|
+
- 私钥必须由客户端先加密,再上传到 custody 服务。
|
|
10
|
+
- custody 服务只保存证书 PEM、加密私钥密文和加密参数元数据,不知道用户密码,也不解密私钥。
|
|
11
|
+
- 恢复时,服务端只返回证书和加密私钥密文;客户端使用自己的密码和 `metadata` 解密。
|
|
12
|
+
|
|
13
|
+
服务地址建议:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
https://aid_custody.{issuer_domain}:{port}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
SDK 使用约束:
|
|
20
|
+
|
|
21
|
+
- `custody_url` 不作为 `AUNClient` 构造配置参数传入。
|
|
22
|
+
- 客户端通过 `client.custody.set_url(...)` 显式配置托管服务地址。
|
|
23
|
+
- 也可以通过 `client.custody.discover_url(aid=...)` 从 `https://{aid}/.well-known/aun-custody` 自动发现官方托管服务地址;发现结果会缓存在当前 `client.custody` 实例中。
|
|
24
|
+
|
|
25
|
+
## 手机号验证码备份/恢复时序
|
|
26
|
+
|
|
27
|
+
备份/绑定手机号:
|
|
28
|
+
|
|
29
|
+
```mermaid
|
|
30
|
+
sequenceDiagram
|
|
31
|
+
autonumber
|
|
32
|
+
participant User as 用户
|
|
33
|
+
participant Client as 已登录设备 AUNClient
|
|
34
|
+
participant Custody as Custody 服务
|
|
35
|
+
participant SMS as 短信服务
|
|
36
|
+
|
|
37
|
+
User->>Client: 输入手机号
|
|
38
|
+
Client->>Custody: POST /custody/accounts/send-code<br/>Authorization: Bearer aun_token<br/>{ phone }
|
|
39
|
+
Custody->>Custody: 校验 AID token,生成绑定验证码
|
|
40
|
+
Custody->>SMS: 发送验证码
|
|
41
|
+
SMS-->>User: 手机验证码
|
|
42
|
+
Custody-->>Client: request_id / expires_in_seconds
|
|
43
|
+
User->>Client: 输入验证码和本地加密口令
|
|
44
|
+
Client->>Client: 本地加密私钥,生成 cert / key / metadata
|
|
45
|
+
Client->>Custody: POST /custody/accounts/bind-phone<br/>Authorization: Bearer aun_token<br/>{ phone, code, cert, key, metadata }
|
|
46
|
+
Custody->>Custody: 校验验证码、证书 AID、保存手机号绑定和密文备份
|
|
47
|
+
Custody-->>Client: binding / backup_result
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
恢复/下载:
|
|
51
|
+
|
|
52
|
+
```mermaid
|
|
53
|
+
sequenceDiagram
|
|
54
|
+
autonumber
|
|
55
|
+
participant User as 用户
|
|
56
|
+
participant Client as 新设备 AUNClient
|
|
57
|
+
participant Custody as Custody 服务
|
|
58
|
+
participant SMS as 短信服务
|
|
59
|
+
|
|
60
|
+
User->>Client: 输入手机号和要恢复的 AID
|
|
61
|
+
Client->>Custody: POST /custody/accounts/send-code<br/>{ phone, aid }
|
|
62
|
+
Custody->>Custody: 校验手机号已绑定该 AID,生成恢复验证码
|
|
63
|
+
Custody->>SMS: 发送验证码
|
|
64
|
+
SMS-->>User: 手机验证码
|
|
65
|
+
Custody-->>Client: request_id / expires_in_seconds
|
|
66
|
+
User->>Client: 输入验证码和本地解密口令
|
|
67
|
+
Client->>Custody: POST /custody/accounts/restore-phone<br/>{ phone, code, aid }
|
|
68
|
+
Custody->>Custody: 校验验证码和绑定关系,读取证书与加密私钥密文
|
|
69
|
+
Custody-->>Client: aid / cert_pem / key_pem / metadata
|
|
70
|
+
Client->>Client: 校验证书归属,使用 metadata 和用户口令解密私钥
|
|
71
|
+
Client->>Client: 导入本地 AID keystore
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## AID token 授权的跨设备复制方案
|
|
75
|
+
|
|
76
|
+
除手机号验证码备份/恢复外,还可以在旧设备仍可用时,基于 AUN 的 AID 认证 token 实现一次性跨设备复制。该方案的核心目标是让 custody 只做短期密文中转:
|
|
77
|
+
|
|
78
|
+
- 旧设备用已通过 AUN 认证的 `aun_token` 证明自己控制该 AID。
|
|
79
|
+
- custody 生成短期复制码,并把复制码绑定到 AID、旧设备认证上下文和过期时间。
|
|
80
|
+
- 旧设备本地生成一次性 OTP,用 OTP 加密私钥后上传密文。
|
|
81
|
+
- 新设备只向 custody 提交 `aid + transfer_code` 领取密文,不提交 OTP。
|
|
82
|
+
- 新设备在本地用 OTP 解密私钥并导入 keystore。
|
|
83
|
+
- custody 在领取成功或过期后销毁复制材料。
|
|
84
|
+
|
|
85
|
+
端点索引:
|
|
86
|
+
|
|
87
|
+
| 端点 | 方法 | 认证 | 说明 |
|
|
88
|
+
|------|------|------|------|
|
|
89
|
+
| `/custody/transfers` | POST | `aun_token` | 旧设备发起复制,返回短期复制码 |
|
|
90
|
+
| `/custody/transfers/{transfer_code}/materials` | POST | `aun_token` | 旧设备上传证书和 OTP 加密后的私钥密文 |
|
|
91
|
+
| `/custody/transfers/claim` | POST | 无 | 新设备凭 `aid + transfer_code` 领取密文材料,不传 OTP |
|
|
92
|
+
|
|
93
|
+
SDK 方法:
|
|
94
|
+
|
|
95
|
+
| 语言 | 发起复制 | 上传复制材料 | 领取复制材料 |
|
|
96
|
+
|------|----------|--------------|--------------|
|
|
97
|
+
| Python | `client.custody.create_device_copy(...)` | `client.custody.upload_device_copy_materials(...)` | `client.custody.claim_device_copy(...)` |
|
|
98
|
+
| JS/TS | `client.custody.createDeviceCopy(...)` | `client.custody.uploadDeviceCopyMaterials(...)` | `client.custody.claimDeviceCopy(...)` |
|
|
99
|
+
| Go | `client.Custody.CreateDeviceCopy(...)` | `client.Custody.UploadDeviceCopyMaterials(...)` | `client.Custody.ClaimDeviceCopy(...)` |
|
|
100
|
+
|
|
101
|
+
复制请求:
|
|
102
|
+
|
|
103
|
+
```http
|
|
104
|
+
POST /custody/transfers
|
|
105
|
+
Content-Type: application/json
|
|
106
|
+
Authorization: Bearer <aun_token>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"aid": "alice.agentid.pub"
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
响应:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"transfer_code": "A7K9Q2ZB",
|
|
120
|
+
"expires_in_seconds": 300,
|
|
121
|
+
"expires_at": 1713000300000
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
上传复制材料:
|
|
126
|
+
|
|
127
|
+
```http
|
|
128
|
+
POST /custody/transfers/A7K9Q2ZB/materials
|
|
129
|
+
Content-Type: application/json
|
|
130
|
+
Authorization: Bearer <aun_token>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"aid": "alice.agentid.pub",
|
|
136
|
+
"cert": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
|
|
137
|
+
"key": "<OTP 加密后的私钥密文>",
|
|
138
|
+
"metadata": {
|
|
139
|
+
"envelope_version": 1,
|
|
140
|
+
"purpose": "device-transfer",
|
|
141
|
+
"encryption": "aes-256-gcm",
|
|
142
|
+
"kdf": "argon2id",
|
|
143
|
+
"kdf_params": {"m": 65536, "t": 3, "p": 4},
|
|
144
|
+
"salt": "...",
|
|
145
|
+
"nonce": "...",
|
|
146
|
+
"otp_hint": "一次性 OTP 只显示给用户,不上传服务端"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
领取复制材料:
|
|
152
|
+
|
|
153
|
+
```http
|
|
154
|
+
POST /custody/transfers/claim
|
|
155
|
+
Content-Type: application/json
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{
|
|
160
|
+
"aid": "alice.agentid.pub",
|
|
161
|
+
"transfer_code": "A7K9Q2ZB"
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
响应:
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"aid": "alice.agentid.pub",
|
|
170
|
+
"cert_pem": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
|
|
171
|
+
"key_pem": "<OTP 加密后的私钥密文>",
|
|
172
|
+
"key_encrypted": true,
|
|
173
|
+
"metadata": {
|
|
174
|
+
"envelope_version": 1,
|
|
175
|
+
"purpose": "device-transfer",
|
|
176
|
+
"encryption": "aes-256-gcm",
|
|
177
|
+
"kdf": "argon2id",
|
|
178
|
+
"kdf_params": {"m": 65536, "t": 3, "p": 4},
|
|
179
|
+
"salt": "...",
|
|
180
|
+
"nonce": "..."
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
跨设备复制时序:
|
|
186
|
+
|
|
187
|
+
```mermaid
|
|
188
|
+
sequenceDiagram
|
|
189
|
+
autonumber
|
|
190
|
+
participant User as 用户
|
|
191
|
+
participant Old as 原有设备 AUNClient
|
|
192
|
+
participant Custody as Custody 服务
|
|
193
|
+
participant New as 新设备 AUNClient
|
|
194
|
+
|
|
195
|
+
Old->>Custody: POST /custody/transfers<br/>Authorization: Bearer aun_token<br/>{ aid }
|
|
196
|
+
Custody->>Custody: 校验 AID token 与 aid 绑定关系
|
|
197
|
+
Custody->>Custody: 生成 transfer_code,绑定 aid,设置 5 分钟 TTL
|
|
198
|
+
Custody-->>Old: transfer_code / expires_in_seconds
|
|
199
|
+
Old->>Old: 生成高熵 OTP,使用 OTP 本地加密私钥
|
|
200
|
+
Old->>Custody: POST /custody/transfers/{transfer_code}/materials<br/>Authorization: Bearer aun_token<br/>{ aid, cert, key, metadata }
|
|
201
|
+
Custody->>Custody: 保存证书和 OTP 加密后的私钥密文,不保存 OTP
|
|
202
|
+
Old-->>User: 显示 aid、transfer_code、OTP
|
|
203
|
+
User->>New: 输入 aid、transfer_code、OTP
|
|
204
|
+
New->>Custody: POST /custody/transfers/claim<br/>{ aid, transfer_code }
|
|
205
|
+
Custody->>Custody: 校验 transfer_code 未过期、未领取且绑定该 aid
|
|
206
|
+
Custody->>Custody: 标记已领取并销毁服务端复制材料
|
|
207
|
+
Custody-->>New: cert_pem / key_pem / metadata
|
|
208
|
+
New->>New: 用 OTP 解密私钥,校验证书 AID 和私钥匹配
|
|
209
|
+
New->>New: 导入本地 AID keystore
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
安全等级判断:
|
|
213
|
+
|
|
214
|
+
- 该方案通常强于单纯手机号验证码恢复,因为发起方必须持有已认证的旧设备 AID token。
|
|
215
|
+
- 该方案的端到端保密性取决于 OTP 强度和客户端加密实现;custody 不拿到 OTP 时,无法解密私钥。
|
|
216
|
+
- 8 位字母数字复制码约 48 bit 熵,只适合作为短期在线领取码;必须配合 5 分钟 TTL、按 AID/IP 限速、错误次数上限和一次性领取。
|
|
217
|
+
- OTP 不能是 6 位短信码级别的低熵密码。建议由旧设备生成至少 128 bit 随机值,再用 Base32、分组字符或助记词展示;并使用 Argon2id 等 KDF 派生加密密钥。
|
|
218
|
+
|
|
219
|
+
主要风险与约束:
|
|
220
|
+
|
|
221
|
+
- 如果旧设备已被攻破,攻击者可发起复制并拿到 OTP;该方案无法抵抗旧设备完全失陷。
|
|
222
|
+
- 如果用户把 `transfer_code + OTP` 同时泄露给攻击者,攻击者可领取并解密私钥。
|
|
223
|
+
- 如果只泄露 `transfer_code`,攻击者可能抢先领取密文并造成拒绝服务;因此领取应一次性、限速,并写审计日志。
|
|
224
|
+
- custody 数据库或日志泄露时,攻击者可拿到短期密文;若 OTP 熵不足,会产生离线爆破风险。
|
|
225
|
+
- 所有请求必须走 HTTPS,客户端必须校验证书,避免转移码被中间人抢用。
|
|
226
|
+
- custody 不得把 OTP、明文私钥、解密后的私钥、完整转移密文写入日志。
|
|
227
|
+
- 新设备导入前必须校验证书归属、证书链、返回的 `aid`、以及解密后的私钥与证书公钥是否匹配。
|
|
228
|
+
- 如果产品语义要求“复制后让旧设备失效”,完成后还需要触发密钥轮换、旧证书吊销或旧设备注销流程;否则旧设备继续可用。
|
|
229
|
+
|
|
230
|
+
CT 日志记录:
|
|
231
|
+
|
|
232
|
+
- `aid.backup`:备份证书和客户端加密私钥时记录。
|
|
233
|
+
- `aid.restore` / `aid.restore_by_phone`:通过会话或手机号验证码恢复时记录。
|
|
234
|
+
- `device_copy.create`:旧设备创建复制会话时记录,不包含复制码。
|
|
235
|
+
- `device_copy.materials_uploaded`:旧设备上传 OTP 加密材料时记录,不包含 OTP 和密文正文。
|
|
236
|
+
- `device_copy.claim`:新设备领取复制材料时记录,不包含 OTP 和密文正文。
|
|
237
|
+
|
|
238
|
+
## 端点索引
|
|
239
|
+
|
|
240
|
+
| 端点 | 方法 | 认证 | 说明 |
|
|
241
|
+
|------|------|------|------|
|
|
242
|
+
| `/custody/accounts/send-code` | POST | 绑定场景需要 `aun_token`;恢复场景不需要 | 发送手机验证码 |
|
|
243
|
+
| `/custody/accounts/bind-phone` | POST | `aun_token` | 绑定手机号并上传 AID 证书、加密私钥 |
|
|
244
|
+
| `/custody/accounts/restore-phone` | POST | 无 | 手机号 + 验证码 + AID 下载证书和加密私钥 |
|
|
245
|
+
| `/custody/transfers` | POST | `aun_token` | 旧设备发起一次性跨设备复制 |
|
|
246
|
+
| `/custody/transfers/{transfer_code}/materials` | POST | `aun_token` | 旧设备上传 OTP 加密后的证书和私钥密文 |
|
|
247
|
+
| `/custody/transfers/claim` | POST | 无 | 新设备领取复制材料,不传 OTP |
|
|
248
|
+
|
|
249
|
+
## send-code
|
|
250
|
+
|
|
251
|
+
发送手机号验证码。该端点兼容两种场景:
|
|
252
|
+
|
|
253
|
+
- 绑定/上传场景:携带 `Authorization: Bearer <aun_token>`,请求体不传 `aid`。
|
|
254
|
+
- 恢复/下载场景:不携带 token,请求体传 `aid`,服务端校验手机号已绑定该 AID。
|
|
255
|
+
|
|
256
|
+
请求:
|
|
257
|
+
|
|
258
|
+
```http
|
|
259
|
+
POST /custody/accounts/send-code
|
|
260
|
+
Content-Type: application/json
|
|
261
|
+
Authorization: Bearer <aun_token>
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
绑定/上传场景:
|
|
265
|
+
|
|
266
|
+
```json
|
|
267
|
+
{
|
|
268
|
+
"phone": "+8613800138000"
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
恢复/下载场景:
|
|
273
|
+
|
|
274
|
+
```json
|
|
275
|
+
{
|
|
276
|
+
"phone": "+8613800138000",
|
|
277
|
+
"aid": "alice.agentid.pub"
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
参数:
|
|
282
|
+
|
|
283
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
284
|
+
|------|------|------|------|
|
|
285
|
+
| `phone` | string | 是 | E.164 格式手机号 |
|
|
286
|
+
| `aid` | string | 否 | 恢复/下载场景填写。填写后不需要 token |
|
|
287
|
+
|
|
288
|
+
响应:
|
|
289
|
+
|
|
290
|
+
```json
|
|
291
|
+
{
|
|
292
|
+
"request_id": "bind-phone-a1b2c3d4e5f6",
|
|
293
|
+
"phone": "+8613800138000",
|
|
294
|
+
"provider": "mock",
|
|
295
|
+
"expires_in_seconds": 300,
|
|
296
|
+
"purpose": "bind-phone",
|
|
297
|
+
"debug_code": "123456"
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
说明:
|
|
302
|
+
|
|
303
|
+
- `debug_code` 只应在开发环境返回;生产环境应为空。
|
|
304
|
+
- 同一手机号和 IP 会受发送频率限制。
|
|
305
|
+
|
|
306
|
+
## bind-phone
|
|
307
|
+
|
|
308
|
+
验证手机验证码,绑定手机号到当前 AID,并上传 AID 证书和客户端加密后的私钥密文。
|
|
309
|
+
|
|
310
|
+
请求:
|
|
311
|
+
|
|
312
|
+
```http
|
|
313
|
+
POST /custody/accounts/bind-phone
|
|
314
|
+
Content-Type: application/json
|
|
315
|
+
Authorization: Bearer <aun_token>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
```json
|
|
319
|
+
{
|
|
320
|
+
"phone": "+8613800138000",
|
|
321
|
+
"code": "123456",
|
|
322
|
+
"cert": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
|
|
323
|
+
"key": "<客户端加密后的私钥密文>",
|
|
324
|
+
"metadata": {
|
|
325
|
+
"encryption": "aes-256-gcm",
|
|
326
|
+
"kdf": "argon2id",
|
|
327
|
+
"kdf_params": {"m": 65536, "t": 3, "p": 4},
|
|
328
|
+
"device": "iPhone 15",
|
|
329
|
+
"note": "主密钥备份"
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
参数:
|
|
335
|
+
|
|
336
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
337
|
+
|------|------|------|------|
|
|
338
|
+
| `phone` | string | 是 | E.164 格式手机号 |
|
|
339
|
+
| `code` | string | 是 | 6 位数字验证码 |
|
|
340
|
+
| `cert` | string | 是 | AID 证书 PEM |
|
|
341
|
+
| `key` | string | 是 | 客户端加密后的私钥密文 |
|
|
342
|
+
| `metadata` | object | 否 | 加密算法、KDF 参数、设备信息等,服务端原样保存 |
|
|
343
|
+
|
|
344
|
+
响应:
|
|
345
|
+
|
|
346
|
+
```json
|
|
347
|
+
{
|
|
348
|
+
"binding": {
|
|
349
|
+
"provider": "phone",
|
|
350
|
+
"external_subject": "+8613800138000",
|
|
351
|
+
"aid": "alice.agentid.pub",
|
|
352
|
+
"status": "active",
|
|
353
|
+
"created_at": 1713000000000,
|
|
354
|
+
"updated_at": 1713000000000
|
|
355
|
+
},
|
|
356
|
+
"backup_result": {
|
|
357
|
+
"aid": "alice.agentid.pub",
|
|
358
|
+
"status": "active",
|
|
359
|
+
"source": "backup",
|
|
360
|
+
"cert_sn": "1a2b3c4d",
|
|
361
|
+
"curve": "P-256",
|
|
362
|
+
"key_encrypted": true,
|
|
363
|
+
"metadata": {"encryption": "aes-256-gcm"}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
校验要求:
|
|
369
|
+
|
|
370
|
+
- `aun_token` 必须能证明当前调用者就是要绑定的 AID。
|
|
371
|
+
- `cert` 中的 CN 必须是该 AID。
|
|
372
|
+
- `key` 必须是密文;服务端不应要求、也不应保存用户密码。
|
|
373
|
+
|
|
374
|
+
## restore-phone
|
|
375
|
+
|
|
376
|
+
凭手机号、验证码和 AID 下载已备份的证书和加密私钥。该接口不要求 AID 登录,因为恢复场景下用户可能已经丢失私钥。
|
|
377
|
+
|
|
378
|
+
请求:
|
|
379
|
+
|
|
380
|
+
```http
|
|
381
|
+
POST /custody/accounts/restore-phone
|
|
382
|
+
Content-Type: application/json
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
```json
|
|
386
|
+
{
|
|
387
|
+
"phone": "+8613800138000",
|
|
388
|
+
"code": "123456",
|
|
389
|
+
"aid": "alice.agentid.pub"
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
参数:
|
|
394
|
+
|
|
395
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
396
|
+
|------|------|------|------|
|
|
397
|
+
| `phone` | string | 是 | E.164 格式手机号 |
|
|
398
|
+
| `code` | string | 是 | 6 位数字验证码 |
|
|
399
|
+
| `aid` | string | 是 | 要恢复的 AID |
|
|
400
|
+
|
|
401
|
+
响应:
|
|
402
|
+
|
|
403
|
+
```json
|
|
404
|
+
{
|
|
405
|
+
"aid": "alice.agentid.pub",
|
|
406
|
+
"cert_pem": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
|
|
407
|
+
"key_pem": "<客户端加密后的私钥密文>",
|
|
408
|
+
"key_encrypted": true,
|
|
409
|
+
"cert_sn": "1a2b3c4d",
|
|
410
|
+
"curve": "P-256",
|
|
411
|
+
"metadata": {
|
|
412
|
+
"encryption": "aes-256-gcm",
|
|
413
|
+
"kdf": "argon2id",
|
|
414
|
+
"kdf_params": {"m": 65536, "t": 3, "p": 4},
|
|
415
|
+
"device": "iPhone 15"
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
客户端处理:
|
|
421
|
+
|
|
422
|
+
1. 校验返回的 `aid` 与请求一致。
|
|
423
|
+
2. 保存 `cert_pem`。
|
|
424
|
+
3. 使用用户密码和 `metadata` 中的 KDF 参数解密 `key_pem`。
|
|
425
|
+
4. 将解密后的私钥导入本地 AID keystore。
|
|
426
|
+
|
|
427
|
+
## 错误语义
|
|
428
|
+
|
|
429
|
+
| HTTP 状态 | 错误码示例 | 说明 |
|
|
430
|
+
|-----------|------------|------|
|
|
431
|
+
| 400 | `invalid_phone` | 手机号格式无效 |
|
|
432
|
+
| 400 | `invalid_code` | 验证码无效或已过期 |
|
|
433
|
+
| 400 | `invalid_crt` | 证书 PEM 无效 |
|
|
434
|
+
| 401 | `invalid_token` | `aun_token` 无效 |
|
|
435
|
+
| 403 | `phone_not_bound` | 手机号未绑定到该 AID |
|
|
436
|
+
| 403 | `wrong_auth_type` | 需要 AID 登录场景却未提供 AID token |
|
|
437
|
+
| 404 | `backup_not_found` | 未找到该 AID 的备份 |
|
|
438
|
+
| 429 | `rate_limited` | 请求频率超限 |
|
|
439
|
+
|
|
440
|
+
## 安全边界
|
|
441
|
+
|
|
442
|
+
- custody 服务不是 AID 身份发行方;AID 身份仍由 auth/CA 体系签发和验证。
|
|
443
|
+
- 手机号不是 AID 身份,只是恢复凭据。
|
|
444
|
+
- 服务端被攻破时,攻击者最多拿到证书和加密私钥密文;密文强度取决于客户端加密算法、用户密码和 KDF 参数。
|
|
445
|
+
- 用户忘记加密密码时,服务端无法解密私钥,也不应提供后门恢复。
|