Skip to content
目录

安全与身份认证

概述

UCP 的安全设计从协议设计开始就考虑了安全性。它支持多种身份认证机制,并提供了强大的消息签名和验证机制,确保消息的完整性、真实性和不可否认性。

身份认证机制

支持的认证方式

UCP 兼容多种身份认证机制:

  1. API Keys:预共享密钥,通过带外交换
  2. OAuth 2.0:客户端凭证或其他 OAuth 流程
  3. mTLS:相互 TLS,使用客户端证书
  4. 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-256ES256SHA-256
P-384ES384SHA-384

实现要求

  • 所有实现必须支持验证 P-256 (ES256) 签名
  • 支持 P-384 (ES384) 是可选的

使用指导

  • 签名者应该使用 P-256 以获得最大兼容性
  • 签名者可以在双方都支持时使用 P-384
  • 算法从 JWK 的 crv 字段派生;alg 包含在 Signature-Input 参数中

密钥格式(JWK)

公钥必须使用 JSON Web Key (JWK) 格式,如 RFC 7517 所定义。

EC 密钥结构

字段类型必需描述
kidstring密钥 ID(在签名中引用)
ktystring密钥类型(EC 用于椭圆曲线)
crvstring是*曲线名称(P-256P-384
xstring是*X 坐标(base64url 编码)
ystring是*Y 坐标(base64url 编码)
usestring密钥用途(sig 用于签名)
algstring算法(ES256ES384

* EC 密钥必需

示例

json
{
  "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

密钥查找流程

  1. 获取签名者的 Profile URL
  2. 获取 Profile(或从缓存提供)
  3. Signature-Input 提取 keyid,匹配 signing_keys[] 中的 kid
  4. 使用相应的公钥验证签名

密钥轮换

要轮换密钥而不中断服务:

  1. 添加新密钥:在 signing_keys[] 中发布新密钥,与现有密钥一起
  2. 开始签名:开始使用新密钥签名
  3. 宽限期:继续接受旧密钥的签名(最少 7 天)
  4. 移除旧密钥:从 signing_keys[] 中移除旧密钥

建议

  • 定期轮换密钥(例如,每年)
  • 在安全事件后立即轮换
  • 监控密钥使用情况

签名计算

签名组件

签名必须包含以下组件:

  1. @method:HTTP 方法
  2. @target-uri:请求目标 URI
  3. @authority:请求的权威(主机)
  4. content-digest:请求体的摘要(RFC 9530)
  5. @signature-params:签名参数(包括 createdexpireskeyid

Content-Digest

请求体使用 Content-Digest(RFC 9530)进行哈希:

http
Content-Digest: sha-256=:base64url(sha256(body_bytes)):

要求

  • 使用原始字节(不是 JSON 字符串)
  • 支持 sha-256(必需)和 sha-512(可选)
  • Signature-Input 中引用为 content-digest

签名输入构造

python
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

签名计算

python
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

http
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):

签名验证

验证流程

python
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 False

Content-Digest 验证

python
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 冲突的请求

验证逻辑

python
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 True

Profile 要求

托管要求

两个 Profile 都必须可靠托管。不可靠或配置错误的 Profile 端点可能阻止另一方处理请求。

  1. Profile 必须通过 HTTPS 提供
  2. Profile 端点不得使用重定向(3xx)
  3. Profile 响应必须包含 Cache-Control header,带有 public 和至少 60 秒的 max-age
  4. Profile 不得使用 privateno-storeno-cache 指令提供

获取要求

商家获取平台 Profile 以执行能力协商和验证身份。UCP 定义了支持无许可集成的最佳实践,但商家保留对其访问策略的完全控制。

获取时应用以下规则

  1. 实现必须拒绝不使用 HTTPS 提供的 Profile URL
  2. 实现不得在 Profile 获取时遵循重定向(3xx)
  3. 实现应该在 Profile 获取上强制执行连接和响应超时
  4. 实现应该缓存 Profile,最小 TTL 为 60 秒,无论源的 Cache-Control header
  5. 实现可以使用 stale-while-revalidate 语义异步刷新 Profile
  6. 在签名验证失败且未知 kid 时,实现可以强制刷新缓存的 Profile——但不得对每个源每个 TTL 下限执行超过一次

发现预算

商家应该建立固定的发现占用空间,以便解析未知平台的资源消耗保持恒定,无论有多少平台请求访问。

策略

  • 固定大小的 Profile 缓存(例如,LRU)——无论遇到多少唯一 Profile URL,都限制内存
  • 全局速率限制在发现获取上——限制出站网络,无需每源状态跟踪
  • 重复失败的回退——减少对持续不可用或恶意 Profile 端点的重试
  • 异步发现——通过响应 503 状态码和 Retry-After header 延迟 Profile 解析,并在后台解析 Profile;当平台重试时,已验证的 Profile 已缓存,能力协商同步进行

错误处理

签名错误

代码描述RESTMCP
signature_missing必需的签名 header/字段不存在401-32000
signature_invalid签名验证失败401-32000
key_not_found在签名者的 signing_keys 中未找到密钥 ID401-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 签名示例

http
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 了解完整规范。

安全最佳实践

对于商家

  1. 验证所有请求:验证签名和身份绑定
  2. 密钥管理:安全存储私钥,定期轮换
  3. Profile 安全:确保 Profile 端点安全可靠
  4. 速率限制:实施速率限制防止滥用
  5. 监控:监控异常活动和失败

对于平台

  1. 签名所有请求:使用 HTTP Message Signatures
  2. 密钥管理:安全存储私钥,定期轮换
  3. Profile 托管:确保 Profile 可靠托管
  4. 错误处理:优雅处理认证失败
  5. 重试逻辑:实现适当的重试和回退

总结

UCP 的安全设计实现了:

  1. 多种认证机制:支持 API Keys、OAuth、mTLS、HTTP Message Signatures
  2. 无许可集成:通过 HTTP Message Signatures 实现
  3. 消息完整性:通过 Content-Digest 和签名确保
  4. 身份验证:通过密钥发现和验证确保
  5. 不可否认性:通过加密签名提供

这种设计使得 UCP 能够:

  • 支持安全的无许可集成
  • 确保消息完整性和真实性
  • 提供强大的身份验证机制
  • 支持高级安全场景(AP2 Mandates)

MIT License.