# 认证与身份

## AID 身份体系

AUN 协议中每个 Agent 持有一个 **AID**（Agent Identity），格式为 `name.issuer`（如 `my-agent.agentid.pub`）。

AID 绑定一对 ECC P-256 密钥和一张 X.509 证书，证书链为：

```
Root CA (P-384) → Registry CA (P-384) → Issuer CA (P-384) → Agent cert (P-256)
```

## 创建 AID

通过 `client.auth.create_aid()` 创建新身份，SDK Core 自动完成：

1. 本地生成 ECC P-256 密钥对
2. 将公钥发送到服务端（`auth.create_aid` RPC）
3. 服务端通过 Issuer CA 签发证书
4. 本地保存私钥、证书和元数据到 KeyStore

```python
MY_AID = f"my-agent-{random.randint(1000,9999)}.agentid.pub"
result = await client.auth.create_aid({"aid": MY_AID})
# result: {"aid": "my-agent-XXXX.agentid.pub", "cert_pem": "-----BEGIN CERTIFICATE-----..."}
```

AID 命名规则（`name.issuer` 格式）：
- `name`：3-64 字节，仅允许 `[a-z0-9_-]`，首字符不允许为 `-`，不能以 `guest` 开头
- `issuer`：合法的可注册域名（如 `agentid.pub`、`company.co.uk`）

创建完成后，身份信息持久化在 `{aun_path}/AIDs/my-agent.agentid.pub/` 目录中，后续可直接认证而无需重新创建。

## 认证（登录）

通过 `client.auth.authenticate()` 完成双向认证。内部会先解析可用 Gateway；在缺省配置下，会基于 AID 通过 Well-Known 自动发现并缓存，再执行认证流程。

```python
auth = await client.auth.authenticate({"aid": "my-agent.agentid.pub"})
# auth: {
#   "aid": "my-agent.agentid.pub",
#   "access_token": "eyJ...",
#   "refresh_token": "eyJ...",
#   "expires_at": 1234567890,
#   "gateway": "wss://gw.agentid.pub/aun"
# }
```

### 两阶段挑战-响应

认证采用双向 ECDSA 挑战-响应机制：

**阶段一**（`auth.aid_login1`）：
- 客户端提交 AID、证书链、客户端 nonce
- 服务端验证客户端证书链，返回服务端 nonce、`request_id`、`auth_cert` 和对 `client_nonce` 的签名 `client_nonce_signature`
- 客户端会先用本地信任根校验 `auth_cert` 链；若中间 CA 缺失，会自动从 Gateway `/pki/chain` 下载补链
- 客户端会继续拉取 Gateway `/pki/crl.json`，校验签名 CRL，并拒绝已吊销的服务端 `auth_cert`
- 客户端会继续拉取 Gateway `/pki/ocsp/{serial}`，校验签名 OCSP 响应，并拒绝非 `good` 状态的服务端 `auth_cert`
- 客户端随后验证 `client_nonce_signature`，确认返回 `auth_cert` 的服务端确实持有对应私钥

**阶段二**（`auth.aid_login2`）：
- 客户端带上 `request_id`、签名服务端 nonce 并提交
- 服务端验证客户端签名，签发 JWT
- 当证书有效期过半时，响应会额外包含 `new_cert` 字段（自动续期的新证书），SDK 自动保存替换旧证书

**`request_id` 是两阶段登录的关联凭据**，必须从 `auth.aid_login1` 响应中取出并原样传给 `auth.aid_login2`。

双向认证确保：
- 服务端确认客户端持有 AID 对应的私钥
- 客户端确认服务端是合法的 Auth 服务，且其 `auth_cert` 链锚定到 SDK 本地信任根

### 签名算法

- P-256 密钥 → ECDSA-SHA256
- P-384 密钥 → ECDSA-SHA384
- `auth.aid_login2` 的签名输入为 `"{nonce}:{client_time}"` 的 UTF-8 字节
- `client_nonce_signature` 的签名输入为原始 `client_nonce` UTF-8 字节
- `client_time` 为客户端发送 login2 时的 Unix 时间戳字符串（当前 Python 实现使用 `time.time()` 秒值字符串）

## JWT 令牌

认证成功后获得两个令牌：

| 令牌 | 用途 | 有效期 |
|------|------|--------|
| `access_token` | 会话认证凭证，每次 RPC 调用携带 | 短期（通常 1 小时） |
| `refresh_token` | 刷新 access_token | 长期（通常 30 天） |

### 令牌自动管理

SDK Core 自动处理令牌生命周期：

- **缓存**：令牌保存在 SecretStore 中（Windows 使用 DPAPI 加密）
- **刷新**：access_token 到期前自动调用 `auth.refresh_token`
- **重认证**：refresh_token 也过期或刷新链耗尽时，使用本地私钥重新执行完整的两阶段认证
- **事件**：刷新成功后发布 `token.refreshed` 事件

### 刷新链限制

服务端对 `auth.refresh_token` 施加刷新链限制：

- **链总时长**：从首次签发起最多 **30 天**
- **刷新次数**：同一链最多 **720 次**
- 达到任一限制后，服务端拒绝刷新并返回错误

SDK 在刷新失败时会自动 fallback 到完整的两阶段认证（`aid_login1` + `aid_login2`），对调用方透明。

## 连接认证

获得令牌后，通过 `client.connect()` 建立认证连接：

```python
# 将 authenticate() 返回的 access_token 传给 connect()
await client.connect({
    "access_token": auth["access_token"],
})
```

连接时 SDK Core 执行：
1. 建立 WebSocket 连接
2. 内部调用 `auth.connect` 完成会话初始化
3. 收到 `status == "ok"` 后进入 READY 状态

## 证书管理

### 默认信任模型

- `aun-sdk-core` 安装包内置根证书
- `root_ca_path` 可额外补充本地 Root CA bundle
- Gateway 仅用于下载缺失的中间 CA 证书和签名状态材料（CRL / OCSP），不作为最终信任锚来源

SDK Core 提供低层证书操作方法（透传到服务端），需要在 `connect()` 之后调用（需要已连接状态）。

### download_cert

按证书序列号下载证书，用于证书链验证。

```python
result = await client.auth.download_cert({"cert_sn": "1234567890ABCDEF"})
```

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `cert_sn` | string | 是 | 证书序列号（十六进制字符串）。特殊值 `"root"` 表示下载 Root CA 证书 |

响应含 `cert`（目标证书 PEM）、`ca_cert`（签发 CA 证书 PEM）、`cert_chain`（完整证书链）。

### renew_cert

证书过期续期，复用原公钥获取新证书。适用于客户端离线较长时间、证书已过期但私钥仍在的场景。

**前置条件**：先调用 `auth.aid_login1` 获取 `nonce` 和 `request_id`。

```python
result = await client.auth.renew_cert({
    "aid": "alice.aid.pub",
    "old_cert": old_cert_pem,
    "signature": signature_b64,       # 用旧私钥对 nonce 的 ECDSA 签名
    "nonce": nonce,                    # 从 aid_login1 获取
    "request_id": request_id,          # 从 aid_login1 获取
})
```

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `aid` | string | 是 | Agent Identifier |
| `old_cert` | string | 是 | 已过期的旧证书（PEM 格式） |
| `signature` | string | 是 | 用旧私钥对 `nonce` 的 ECDSA 签名（Base64） |
| `nonce` | string | 是 | 从 `auth.aid_login1` 获取的一次性 nonce |
| `request_id` | string | 是 | 与 `auth.aid_login1` 中相同 |

**安全约束**：宽限期 ≤ 90 天，超过宽限期须重新 `create_aid`。旧证书必须未被吊销。

### rekey

密钥轮转，更换公私钥对并获取新证书。适用于私钥可能泄露、设备迁移或定期安全轮转。

**前置条件**：先调用 `auth.aid_login1` 获取 `nonce` 和 `request_id`。

```python
result = await client.auth.rekey({
    "aid": "alice.aid.pub",
    "new_public_key": new_pub_key_b64,  # 新公钥 SPKI Base64
    "old_cert": old_cert_pem,
    "signature": signature_b64,          # 用旧私钥对 nonce+new_public_key 的签名
    "nonce": nonce,
    "request_id": request_id,
})
```

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `aid` | string | 是 | Agent Identifier |
| `new_public_key` | string | 是 | 新公钥（SPKI Base64 格式） |
| `old_cert` | string | 是 | 当前证书（有效或宽限期内过期均可） |
| `signature` | string | 是 | 用旧私钥对 `nonce + new_public_key` 的 ECDSA 签名（Base64） |
| `nonce` | string | 是 | 从 `auth.aid_login1` 获取的一次性 nonce |
| `request_id` | string | 是 | 与 `auth.aid_login1` 中相同 |

**安全约束**：签名内容包含新公钥（`nonce + new_public_key`），防止中间人替换。rekey 成功后旧证书立即吊销。客户端完成后应销毁旧私钥。

### request_cert

为已有 AID 申请不同曲线的额外证书。必须在 `auth.connect` 成功后的已认证会话上调用（不能像 `aid_login1/2` 那样走 pre-auth RPC）。

```python
result = await client.auth.request_cert({
    "aid": "alice.aid.pub",
    "public_key": pub_key_b64,
    "curve": "P-384",
})
```

### trust_roots

获取信任根证书列表。

```python
result = await client.auth.trust_roots({})
