安全与身份认证
概述
UCP 的安全设计从协议设计开始就考虑了安全性。它支持多种身份认证机制,并提供了强大的消息签名和验证机制,确保消息的完整性、真实性和不可否认性。
身份认证机制
支持的认证方式
UCP 兼容多种身份认证机制:
- API Keys:预共享密钥,通过带外交换
- OAuth 2.0:客户端凭证或其他 OAuth 流程
- mTLS:相互 TLS,使用客户端证书
- HTTP Message Signatures:基于 RFC 9421 的加密签名
HTTP Message Signatures(推荐)
HTTP Message Signatures 支持无许可集成——商家可以通过广告的公钥验证平台,而无需协商共享密钥。
优势:
- 无需预先建立信任关系
- 支持动态发现和验证
- 基于标准(RFC 9421)
- 提供不可否认性
其他机制需要预先凭证交换,意味着预先建立的关系。
HTTP Message Signatures
架构
UCP 使用 HTTP Message Signatures(RFC 9421)保护所有基于 HTTP 的传输:
┌─────────────────────────────────────────────────────────┐
│ SHARED FOUNDATION │
├─────────────────────────────────────────────────────────┤
│ Signature Format: RFC 9421 │
│ Body Digest: RFC 9530 (Content-Digest) │
│ Algorithms: ES256 (required), ES384 (optional) │
│ Key Format: JWK (RFC 7517) │
│ Key Discovery: signing_keys[] in /.well-known/ucp │
│ Replay Protection: idempotency-key │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ HTTP TRANSPORTS │
├─────────────────────────────────────────────────────────┤
│ REST API: Standard HTTP requests │
│ MCP: Streamable HTTP transport (JSON-RPC over HTTP) │
├─────────────────────────────────────────────────────────┤
│ Headers: │
│ Signature-Input (describes signed components) │
│ Signature (contains signature value) │
│ Content-Digest (body hash, raw bytes) │
└─────────────────────────────────────────────────────────┘签名算法
UCP 支持 ECDSA 签名,使用以下曲线:
| 曲线 | JWK alg | 哈希 |
|---|---|---|
| P-256 | ES256 | SHA-256 |
| P-384 | ES384 | SHA-384 |
实现要求:
- 所有实现必须支持验证 P-256 (
ES256) 签名 - 支持 P-384 (
ES384) 是可选的
使用指导:
- 签名者应该使用 P-256 以获得最大兼容性
- 签名者可以在双方都支持时使用 P-384
- 算法从 JWK 的
crv字段派生;alg不包含在Signature-Input参数中
密钥格式(JWK)
公钥必须使用 JSON Web Key (JWK) 格式,如 RFC 7517 所定义。
EC 密钥结构:
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
kid | string | 是 | 密钥 ID(在签名中引用) |
kty | string | 是 | 密钥类型(EC 用于椭圆曲线) |
crv | string | 是* | 曲线名称(P-256、P-384) |
x | string | 是* | X 坐标(base64url 编码) |
y | string | 是* | Y 坐标(base64url 编码) |
use | string | 否 | 密钥用途(sig 用于签名) |
alg | string | 否 | 算法(ES256、ES384) |
* EC 密钥必需
示例:
{
"kid": "key-2024-01-15",
"kty": "EC",
"crv": "P-256",
"x": "WKn-ZIGevcwGIyyrzFoZNBdaq9_TsqzGl96oc0CWuis",
"y": "y77t-RvAHRKTsSGdIYUfweuOvwrvDD-Q3Hv5J0fSKbE",
"use": "sig",
"alg": "ES256"
}密钥发现
公钥发布在参与者的 UCP Profile 的 signing_keys 数组中,位于 /.well-known/ucp。
密钥查找流程:
- 获取签名者的 Profile URL
- 获取 Profile(或从缓存提供)
- 从
Signature-Input提取keyid,匹配signing_keys[]中的kid - 使用相应的公钥验证签名
密钥轮换
要轮换密钥而不中断服务:
- 添加新密钥:在
signing_keys[]中发布新密钥,与现有密钥一起 - 开始签名:开始使用新密钥签名
- 宽限期:继续接受旧密钥的签名(最少 7 天)
- 移除旧密钥:从
signing_keys[]中移除旧密钥
建议:
- 定期轮换密钥(例如,每年)
- 在安全事件后立即轮换
- 监控密钥使用情况
签名计算
签名组件
签名必须包含以下组件:
@method:HTTP 方法@target-uri:请求目标 URI@authority:请求的权威(主机)content-digest:请求体的摘要(RFC 9530)@signature-params:签名参数(包括created、expires、keyid)
Content-Digest
请求体使用 Content-Digest(RFC 9530)进行哈希:
Content-Digest: sha-256=:base64url(sha256(body_bytes)):要求:
- 使用原始字节(不是 JSON 字符串)
- 支持
sha-256(必需)和sha-512(可选) - 在
Signature-Input中引用为content-digest
签名输入构造
def construct_signing_input(method, target_uri, authority, content_digest, created, expires, keyid):
"""构造签名输入字符串"""
# 规范化组件值
components = [
f'"@method": {method}',
f'"@target-uri": {target_uri}',
f'"@authority": {authority}',
f'"content-digest": {content_digest}',
]
# 签名参数
params = [
f'created={created}',
f'expires={expires}',
f'keyid="{keyid}"'
]
# 组合
signature_input = '; '.join(components) + '; ' + '; '.join(params)
return signature_input签名计算
import hashlib
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import serialization
def sign_request(signing_input, private_key, keyid, algorithm='ES256'):
"""计算请求签名"""
# 根据算法选择哈希
if algorithm == 'ES256':
hash_algorithm = hashes.SHA256()
elif algorithm == 'ES384':
hash_algorithm = hashes.SHA384()
else:
raise ValueError(f"Unsupported algorithm: {algorithm}")
# 签名
signature_bytes = private_key.sign(
signing_input.encode('utf-8'),
ec.ECDSA(hash_algorithm)
)
# Base64url 编码
signature_b64 = base64url_encode(signature_bytes)
return signature_b64签名 Header
Signature-Input: sig1=("@method" "@target-uri" "@authority" "content-digest" "@signature-params");created=1699909200;expires=1699912800;keyid="key-2024-01-15"
Signature: sig1=:base64url(signature_bytes):签名验证
验证流程
def verify_signature(request, business_profile):
"""验证请求签名"""
# 1. 解析 Signature-Input
signature_input_header = request.headers.get('Signature-Input')
signature_header = request.headers.get('Signature')
if not signature_input_header or not signature_header:
raise ValueError("Missing signature headers")
# 2. 提取 keyid
keyid = extract_keyid(signature_input_header)
# 3. 从 Profile 获取公钥
public_key = get_key_by_kid(business_profile['signing_keys'], keyid)
if not public_key:
raise ValueError(f"Key not found: {keyid}")
# 4. 重构签名输入
signing_input = reconstruct_signing_input(request, signature_input_header)
# 5. 验证签名
signature_bytes = base64url_decode(extract_signature_value(signature_header))
try:
public_key.verify(
signature_bytes,
signing_input.encode('utf-8'),
ec.ECDSA(hashes.SHA256())
)
return True
except InvalidSignature:
return FalseContent-Digest 验证
def verify_content_digest(request_body, content_digest_header):
"""验证请求体摘要"""
# 计算请求体摘要
body_hash = hashlib.sha256(request_body).digest()
body_digest = f"sha-256=:{base64url_encode(body_hash)}:"
# 比较
return body_digest == content_digest_header身份绑定
要求
无论使用哪种认证机制,验证者必须确保认证的身份与 UCP-Agent header 一致:
- HTTP Message Signatures:签名者的 Profile(来自
UCP-Agent)通过签名验证验证;无需额外检查 - API keys / OAuth / mTLS:验证者必须确认认证的主体被授权代表
UCP-Agent中标识的 Profile 行事。拒绝认证身份与声明的 Profile 冲突的请求
验证逻辑
def verify_identity_binding(authenticated_identity, ucp_agent_profile_uri):
"""验证身份绑定"""
# 获取声明的 Profile
claimed_profile = fetch_profile(ucp_agent_profile_uri)
# 验证认证身份是否被授权代表声明的 Profile
if authenticated_identity not in claimed_profile['authorized_identities']:
raise ValueError("Identity not authorized for profile")
return TrueProfile 要求
托管要求
两个 Profile 都必须可靠托管。不可靠或配置错误的 Profile 端点可能阻止另一方处理请求。
- Profile 必须通过 HTTPS 提供
- Profile 端点不得使用重定向(3xx)
- Profile 响应必须包含
Cache-Controlheader,带有public和至少 60 秒的max-age - Profile 不得使用
private、no-store或no-cache指令提供
获取要求
商家获取平台 Profile 以执行能力协商和验证身份。UCP 定义了支持无许可集成的最佳实践,但商家保留对其访问策略的完全控制。
获取时应用以下规则:
- 实现必须拒绝不使用 HTTPS 提供的 Profile URL
- 实现不得在 Profile 获取时遵循重定向(3xx)
- 实现应该在 Profile 获取上强制执行连接和响应超时
- 实现应该缓存 Profile,最小 TTL 为 60 秒,无论源的
Cache-Controlheader - 实现可以使用 stale-while-revalidate 语义异步刷新 Profile
- 在签名验证失败且未知
kid时,实现可以强制刷新缓存的 Profile——但不得对每个源每个 TTL 下限执行超过一次
发现预算
商家应该建立固定的发现占用空间,以便解析未知平台的资源消耗保持恒定,无论有多少平台请求访问。
策略:
- 固定大小的 Profile 缓存(例如,LRU)——无论遇到多少唯一 Profile URL,都限制内存
- 全局速率限制在发现获取上——限制出站网络,无需每源状态跟踪
- 重复失败的回退——减少对持续不可用或恶意 Profile 端点的重试
- 异步发现——通过响应
503状态码和Retry-Afterheader 延迟 Profile 解析,并在后台解析 Profile;当平台重试时,已验证的 Profile 已缓存,能力协商同步进行
错误处理
签名错误
| 代码 | 描述 | REST | MCP |
|---|---|---|---|
signature_missing | 必需的签名 header/字段不存在 | 401 | -32000 |
signature_invalid | 签名验证失败 | 401 | -32000 |
key_not_found | 在签名者的 signing_keys 中未找到密钥 ID | 401 | -32000 |
digest_mismatch | 体摘要与 Content-Digest header 不匹配 | 400 | -32600 |
algorithm_unsupported | 签名算法不受支持 | 400 | -32600 |
协议错误
| HTTP | 描述 | MCP |
|---|---|---|
| 401 | 需要认证或凭证无效 | -32000 |
| 403 | 已认证但权限不足 | -32000 |
| 409 | 幂等性密钥与不同 payload 重用 | -32000 |
| 429 | 请求过多 | -32000 |
| 500 | 意外服务器错误 | -32603 |
| 503 | 服务器暂时无法处理请求 | -32000 |
对于 MCP over HTTP,HTTP 状态码是主要信号;JSON-RPC error.code 提供次要信号。两种传输应该为 429 和 503 响应包含 Retry-After header(REST)或 error.data.retry_after(MCP)。
Webhook 签名
要求
商家到平台的 webhook 必须签名。参见 Message Signatures — When Signatures Apply。
Webhook 签名示例
POST /webhooks/ucp/orders HTTP/1.1
Host: platform.example.com
Content-Type: application/json
Signature-Input: sig1=("@method" "@target-uri" "@authority" "content-digest" "@signature-params");created=1699909200;expires=1699912800;keyid="business_2025"
Signature: sig1=:base64url(signature_bytes):
{
"order": {
"id": "order_123",
"status": "shipped",
...
}
}AP2 Mandates 扩展
对于需要加密授权证明的场景(如自主 AI 代理),UCP 支持 AP2 Mandates 扩展(dev.ucp.shopping.ap2_mandate)。
特点
- 商家授权:商家在结账响应中嵌入加密签名
- 结账授权:平台提供加密签名的授权证明
- 不可否认性:提供强加密保证,证明交易详情和参与者同意
参见 AP2 Mandates Extension 了解完整规范。
安全最佳实践
对于商家
- 验证所有请求:验证签名和身份绑定
- 密钥管理:安全存储私钥,定期轮换
- Profile 安全:确保 Profile 端点安全可靠
- 速率限制:实施速率限制防止滥用
- 监控:监控异常活动和失败
对于平台
- 签名所有请求:使用 HTTP Message Signatures
- 密钥管理:安全存储私钥,定期轮换
- Profile 托管:确保 Profile 可靠托管
- 错误处理:优雅处理认证失败
- 重试逻辑:实现适当的重试和回退
总结
UCP 的安全设计实现了:
- 多种认证机制:支持 API Keys、OAuth、mTLS、HTTP Message Signatures
- 无许可集成:通过 HTTP Message Signatures 实现
- 消息完整性:通过 Content-Digest 和签名确保
- 身份验证:通过密钥发现和验证确保
- 不可否认性:通过加密签名提供
这种设计使得 UCP 能够:
- 支持安全的无许可集成
- 确保消息完整性和真实性
- 提供强大的身份验证机制
- 支持高级安全场景(AP2 Mandates)