先看结论与判断条件

  • 平台证明回答应用实例与运行环境的风险问题,模型权益回答谁可在什么条件下使用哪个模型,两者必须由服务端显式组合。
  • 挑战值、业务请求摘要、用户会话、模型版本和权益令牌需要共享可核对的请求标识,任一字段不一致都应拒绝。
  • App Attest 与 Play Integrity 的响应都应由后端验证,客户端展示成功状态不能成为模型密钥或下载地址的放行依据。
  • DPoP 可以把访问令牌绑定到客户端持有的密钥,但不能证明设备绝对可信,也不能替代受众、期限和业务权限校验。
  • 模型文件的摘要、版本和更新元数据应进入权益决策,避免合法用户拿旧授权加载未批准、回滚或混搭的模型产物。
  • 验收记录必须区分平台事实、工程设计和项目实测;没有真实证明回执与候选包证据时,只能确认方案完整,不能宣称攻击已被阻断。

先拆开应用实例证明与模型权益

应用实例证明与模型授权常被画成一个“可信设备即可下载”的判断框,但它们回答的是不同问题。平台证明尝试说明请求是否来自预期应用实例,并返回与平台环境相关的风险信号;模型权益则要判断当前用户、账户、套餐、地区、模型版本、使用场景和有效期是否满足业务规则。前者通过不代表后者自然成立,后者有效也不应跳过前者要求的请求绑定。

把两者混成一个布尔值会产生难以审计的漏洞。例如,客户端可能拿一次成功证明反复请求多个模型版本,也可能把甲会话取得的证明结果与乙账户的权益令牌拼接。服务端若只检查“证明通过”和“令牌未过期”,却没有核对它们是否属于同一业务动作,就无法区分合法重试与跨请求挪用。设计文档应把每个输入、签发方、受众、时效和绑定字段逐项列出。

合理的信任边界是:移动端负责生成平台要求的证明材料、持有本机密钥并提交业务请求;证明服务负责给出可验证的完整性信号;授权服务核对用户权益、请求上下文与模型身份;模型分发或推理服务只接受授权服务签发且受众正确的短期令牌。任何客户端本地开关、缓存状态或界面提示都只能改善体验,不能替代服务端决策。

证明信号与模型权益各自承担的责任
对象主要回答不能单独证明服务端最低检查
应用实例证明请求是否来自预期应用实例及平台环境用户已购买模型或允许访问该版本证明链、挑战、请求摘要、时效与重放状态
用户会话当前请求对应哪个已认证主体客户端实例没有被替换或复制会话状态、主体、认证强度与撤销记录
模型权益主体可使用哪个模型及操作请求来自可信应用实例模型标识、版本、动作、限制与到期时间
持有证明令牌调用方持有与令牌绑定的密钥设备环境或业务身份绝对可信公钥拇指印、方法、目标 URI、时间和唯一标识
模型产物元数据下载或加载对象的版本和摘要用户仍具备当前商业权限签名、版本、过期、一致性与目标摘要
服务端审计事件系统为何允许或拒绝一次动作所有下游执行都已成功决策码、绑定标识、策略版本与证据引用

用规范化业务请求生成唯一绑定材料

绑定的起点不是平台 API,而是一份能够稳定序列化的业务请求。它至少包含请求标识、主体会话引用、操作类型、模型标识、模型版本、客户端生成的公钥引用、服务端挑战和过期时间。字段顺序、编码、空值处理和字符串规范必须固定,否则客户端与服务端计算的摘要会因为表示差异而不一致。摘要输入还应带协议版本,防止未来增删字段时旧客户端产生含义不同但格式相同的请求。

请求标识应由服务端生成或由服务端认可,并只在短窗口内使用一次。挑战记录需要保存签发时间、用途、已消费状态和预期会话,验证成功后以原子方式标记消费。仅检查挑战字符串是否存在不够,因为并发提交可能在消费状态落库前同时通过。数据库更新应带“当前仍未消费且未过期”的条件,只有成功更新一行的请求才进入后续授权。

Play Integrity 标准请求提供 requestHash,用于把完整性响应绑定到业务请求。工程上应对规范化请求字节计算摘要,把同样字节或可重建字段保存在服务端,并在收到响应后重算。Apple App Attest 的服务端验证同样需要让断言覆盖与这次操作相关的客户端数据。两种平台的字段与流程不同,但共同原则是证明必须靠近受保护动作,不能使用一份脱离业务上下文的长期“设备可信”缓存。

建议进入绑定信封的字段
字段来源参与的校验失败处理
request_id授权服务挑战、证明、会话和权益记录必须一致不一致直接拒绝并记录决策码
challenge授权服务证明材料必须覆盖当前未消费挑战过期或已消费时重新发起流程
subject_ref认证服务会话主体与权益主体必须一致禁止客户端自行替换主体
model_id 与 version业务请求权益范围与产物元数据必须精确匹配版本缺失或越界时不下发令牌
operation业务请求下载、解密、推理等动作分别授权令牌不得跨动作复用
audience授权服务令牌只能交给指定资源服务资源服务拒绝其他受众
expires_at授权服务挑战、证明与权益均在各自窗口内过期后重新证明而非本地延长

分别处理 Apple 与 Android 的平台证明

Apple 的 App Attest 流程要求服务器验证证明对象与后续断言,并将断言关联到具体服务器请求。实现时应保存应用实例公钥、平台返回材料、计数或重放相关状态以及验证结果,但不要把客户端返回的“验证成功”当成事实。服务端还要核对挑战是否属于当前会话和模型动作。应用升级、密钥重新生成、设备迁移或平台服务暂时不可用时,系统需要明确是重新注册、风险降级还是拒绝,而不是静默放宽。

Play Integrity 的标准请求强调在受保护动作附近准备完整性令牌,并可通过 requestHash 绑定业务请求。后端解码并验证响应后,还需将返回的应用、账户或设备相关信号映射到自己的风险策略。Google 的概览把完整性 API 定位为整体反滥用策略的一部分,这意味着单一 verdict 不应被提升为绝对可信根;异常信号、调用频率、账户状态与模型权益仍要共同参与最终决定。

跨平台层不应强行把两套响应压成相同字段后丢弃原始语义。可建立统一的决策输入,例如 provider、verification_status、request_binding_status、freshness_status、replay_status 和 policy_tier,同时保存平台特定证据引用。这样上层权益服务可以复用状态机,底层验证器仍按官方规则处理。遇到新平台信号或字段含义变化时,只调整适配器和策略版本,不篡改历史审计事件。

  • 证明材料只由后端验证,客户端结果不直接放行
  • 每份证明都能反查到当前挑战和业务请求摘要
  • 挑战消费使用原子条件更新避免并发重放
  • 平台特定验证证据保留引用而非只存布尔值
  • 平台服务异常时采用明确的拒绝或有限降级策略
  • 应用实例密钥轮换、丢失和重新注册都有状态迁移
  • 策略日志区分验证失败、权益失败和资源服务失败

让服务端状态机完成最终授权

授权服务应把一次模型请求拆成可观察的状态:挑战已签发、证明已验证、挑战已消费、会话已确认、权益已匹配、产物身份已确认、短期令牌已签发以及资源服务已接受。状态转换必须由服务端事件触发,并携带策略版本与失败原因。客户端可以重试网络请求,但不能直接把状态从“证明待验证”推进到“权益有效”,也不能依靠修改本地时间延长服务器记录的有效期。

放行规则应采用全部必要条件同时成立,而不是给信号打一个含糊总分。高价值模型下载可以要求请求绑定通过、证明新鲜、会话有效、权益覆盖精确版本、模型元数据未过期且受众匹配。低风险的公开模型目录查询可以不要求平台证明。按动作分级能减少无意义证明调用,也能避免为追求可用性而在核心下载接口上设置不透明的“失败也放行”。

拒绝结果需要可恢复但不能泄露策略细节。客户端可接收稳定的公开错误类别,如会话需刷新、证明需重试、版本不可用或权限不足;内部审计则保留更具体的验证步骤和策略命中项。不要向客户端返回哪个完整性字段失败、风险阈值或可用于试探策略的细粒度分数。支持人员凭请求标识查询内部事件即可,不应要求用户上传完整证明对象或令牌。

模型权益状态机的关键转移
当前状态必要输入允许转移拒绝条件
挑战已签发request_id、会话、模型动作、到期时间进入证明待验证主体或模型动作缺失
证明待验证平台材料、挑战、请求摘要进入证明通过签名、绑定、时效或重放检查失败
证明通过有效会话与主体进入权益待匹配会话撤销、主体不一致或认证不足
权益待匹配模型、版本、操作与商业策略进入产物待确认套餐、地区、版本或动作不覆盖
产物待确认签名元数据、摘要、版本与期限签发短期权益令牌回滚、过期、混搭或摘要不匹配
令牌已签发受众、持有密钥与有效期资源服务接受请求受众错误、持有证明无效或令牌过期
资源已交付服务端使用回执登记完成或受限重试重复下载超限或请求上下文变化

用 DPoP 限制权益令牌被搬到别处使用

短期令牌如果只是 bearer token,拿到它的一方通常可以在有效期内直接使用。RFC 9449 定义的 DPoP 让客户端针对 HTTP 请求生成持有证明,并把访问令牌绑定到相应公钥。资源服务检查证明的方法、目标 URI、时间、唯一标识与令牌确认字段,可以降低令牌被复制到另一客户端后重放的风险。模型下载和远程推理接口可把这类发送者约束作为纵深防护。

DPoP 不等于设备完整性证明。密钥可能由被控制的应用进程合法调用,平台证明也可能只覆盖某个时间点。两者应在授权时通过请求标识与公钥引用关联:平台证明验证的是当前应用实例和业务请求,权益令牌记录允许的模型与动作,DPoP 证明调用方仍持有签发时绑定的密钥。任何一层缺失都不能用另一层的成功来补齐。

RFC 9700 汇总了 OAuth 部署中的重放、授权码注入、重定向和不安全授权模式等风险。对于模型权益接口,应缩小令牌受众和 scope,不把下载、解密、远程推理、管理配置合并为一个宽权限令牌;刷新流程也要重新检查撤销状态和高风险上下文。工程判断是,高风险模型版本发生变化时应重新授权,不让旧令牌通过模糊版本范围自动覆盖新资产。

令牌声明与资源服务检查边界
声明或证明绑定对象资源服务检查不能替代
audience指定下载或推理服务当前服务标识精确匹配用户权益与模型版本检查
scope 或 operation下载、解密、推理等动作请求动作位于允许集合平台证明与会话有效性
model_id 与 version批准的具体模型产物路径、元数据和摘要对应同一版本更新元数据签名验证
subject用户或业务主体与当前会话和资源所有者一致应用实例完整性判断
confirmation key客户端持有的公钥DPoP 证明与确认字段匹配绝对设备可信或防止进程内滥用
expiry 与 issued-at有限使用窗口服务器时间与撤销状态有效请求唯一性和挑战消费
request_id整条授权事务审计事件与模型请求一致令牌签名和受众校验

把模型版本和更新元数据纳入权益判断

模型权益不能只写一个产品名称。量化方式、分词器、适配器、系统提示模板和运行时依赖变化,都可能让同名模型成为不同交付对象。授权服务应使用不可歧义的 model_id、version 与 artifact_digest,资源服务根据这组三元组返回产物。客户端收到文件后仍需验证签名元数据与摘要,不能因为下载连接来自合法域名就跳过本地身份检查。

The Update Framework 的规范通过角色化签名元数据、版本、过期时间和一致性关系来处理更新中的回滚、冻结与混搭风险。移动模型系统可以借用这些设计原则,把目标元数据、快照引用和到期策略纳入下载验证。不过 TUF 解决的是更新信任链与元数据一致性,不会判断某个账户是否购买模型,也不会替权益服务决定允许的推理次数。

版本绑定还要覆盖缓存和并发。若客户端已经缓存旧模型,服务端不能只返回“用户有权限”而忽略旧版本是否仍获准;若新版本正在分批发布,权益令牌必须指明允许的版本集合或唯一版本,并与下载元数据一致。回滚只应由签名且未过期的策略显式批准。任何本地修改版本号、复制元数据或复用旧下载地址的行为都不应改变服务端授权结果。

检查证明、会话与模型权益是否绑定同一业务请求
#!/usr/bin/env python3
import json
import re
import sys
from pathlib import Path

REQUEST_PATTERN = re.compile(r"^[a-f0-9]{32}$")
MODEL_PATTERN = re.compile(r"^[a-z0-9][a-z0-9._-]{2,63}$")
ALLOWED_OPERATIONS = {"download", "remote_inference"}

def require_text(record, field):
    value = record.get(field)
    if not isinstance(value, str) or not value.strip():
        print("missing field: " + field, file=sys.stderr)
        raise SystemExit(3)
    return value.strip()

def validate(record):
    request_id = require_text(record, "request_id")
    if not REQUEST_PATTERN.fullmatch(request_id):
        print("invalid request_id", file=sys.stderr)
        raise SystemExit(4)
    bound_ids = {
        require_text(record, "attestation_request_id"),
        require_text(record, "session_request_id"),
        require_text(record, "entitlement_request_id"),
    }
    if bound_ids != {request_id}:
        print("request binding mismatch", file=sys.stderr)
        raise SystemExit(5)
    model_version = require_text(record, "model_version")
    entitlement_version = require_text(record, "entitlement_model_version")
    if model_version != entitlement_version:
        print("model version mismatch", file=sys.stderr)
        raise SystemExit(6)
    if not MODEL_PATTERN.fullmatch(model_version):
        print("unsafe model version", file=sys.stderr)
        raise SystemExit(7)
    operation = require_text(record, "operation")
    if operation not in ALLOWED_OPERATIONS:
        print("operation not allowed", file=sys.stderr)
        raise SystemExit(8)
    audience = require_text(record, "audience")
    expected_audience = require_text(record, "expected_audience")
    if audience != expected_audience:
        print("audience mismatch", file=sys.stderr)
        raise SystemExit(9)
    expires_at = record.get("expires_at")
    evaluated_at = record.get("evaluated_at")
    if not isinstance(expires_at, int) or not isinstance(evaluated_at, int):
        print("timestamps must be integers", file=sys.stderr)
        raise SystemExit(10)
    if expires_at <= evaluated_at:
        print("entitlement expired", file=sys.stderr)
        raise SystemExit(11)

def main():
    if len(sys.argv) != 2:
        print("Usage: entitlement_check.py INPUT_JSON", file=sys.stderr)
        raise SystemExit(2)
    input_path = Path(sys.argv[1])
    if not input_path.is_file():
        print("input file not found", file=sys.stderr)
        raise SystemExit(2)
    try:
        record = json.loads(input_path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError) as exc:
        print("cannot read input: " + str(exc), file=sys.stderr)
        raise SystemExit(2)
    if not isinstance(record, dict):
        print("input must be an object", file=sys.stderr)
        raise SystemExit(2)
    validate(record)
    print("binding accepted")

if __name__ == "__main__":
    main()

设计失败、重试、离线和撤销路径

证明调用会遇到网络中断、平台限流、系统时间偏差、应用实例密钥丢失和服务端验证器升级。失败策略必须按动作风险设计。公开目录可以返回缓存,已下载且仍在有效窗口内的模型可以维持受限功能,高价值新模型下载则应在证明或权益状态不确定时拒绝。所谓有限降级必须写清允许的模型版本、截止时间、功能范围和服务器撤销优先级,不能用一个长期本地开关表示。

重试需要保持幂等。客户端重发同一 request_id 时,服务端可以返回同一最终结果或明确告知挑战已消费,但不能重复签发语义不同的权益令牌。若业务上下文、会话、模型版本或持有密钥发生变化,应创建新请求并重新证明。网络超时后先查询事务状态,再决定是否提交新请求,可以避免客户端把未收到响应误判为服务端未处理。

撤销至少覆盖账户权益撤销、应用实例密钥撤销、模型版本撤回、令牌撤销和策略紧急收紧。资源服务不能只依赖令牌自然过期;对高风险事件可检查短期撤销缓存或授权服务状态。离线模型无法获得实时撤销,因此离线窗口本质上是业务接受的剩余风险。设计评审应记录最大窗口和重新联网条件,而不是声称本地加密可以消除离线授权风险。

  • 每种失败码都有公开恢复动作和内部审计原因
  • 同一请求重试不会签发语义不同的权益令牌
  • 请求上下文变化时强制生成新挑战与请求标识
  • 平台服务不可用时按动作风险决定拒绝或有限降级
  • 离线窗口绑定明确模型版本、截止时间和功能范围
  • 账户、实例、模型、令牌和策略均有独立撤销入口
  • 客户端时间只作体验提示,最终期限由服务端时间判断

用可复核证据验收整条授权链

验收不应只截取一次“授权成功”响应。测试包需要固定应用版本与签名身份,建立请求信封样本,记录挑战签发、平台验证、会话核对、权益匹配、模型元数据确认、令牌签发和资源服务接受的连续事件。每个事件只保存公开安全的请求引用、状态、策略版本和时间,不保存完整令牌或平台证明对象。这样既能复盘决策,也不会让日志成为新的凭据仓库。

负向用例应围绕绑定关系,而不是尝试复现攻击链。可以交换请求标识、替换模型版本、改变令牌受众、重复使用已消费挑战、使用过期事务或撤销后的主体,确认每一种不一致都在预期层被拒绝。测试结论要写清观察点:授权服务拒绝不等于模型文件已从设备删除,资源服务拒绝也不证明客户端内存中的历史明文不存在。不同责任边界需要不同证据。

当前没有具体应用候选包、真实平台证明回执、权益数据库快照和资源服务日志,因此本文只能给出可执行架构与检查清单,不能断言某个产品已经阻断重放或模型盗用。项目落地时应把同一候选身份、测试账户、策略版本和事件链装入受控证据包,门禁通过后再扩大流量;任何重新构建、重签名或模型元数据变化都需要重新验收。

发布前需要收集的最小证据
证据项验证问题合格形态结论边界
候选身份证明是否对应实际交付应用版本、签名身份与受控摘要一致不代表业务授权策略正确
请求信封各层是否使用同一 request_id规范化字段和摘要可重算不包含真实令牌与完整证明对象
挑战消费记录并发与重试是否可能重放只有一次原子消费成功只证明服务端状态机行为
平台验证事件证明是否绑定当前业务请求验证状态、时效与证据引用完整平台信号不是绝对设备可信
权益决策事件主体、动作和模型版本是否覆盖策略版本与允许或拒绝原因清楚不证明资源服务已经执行
资源服务记录受众、持有证明和产物身份是否一致请求引用与模型摘要可追溯不证明客户端历史缓存已清除
负向用例字段替换、过期和撤销是否拒绝预期层返回稳定失败类别不应公开可操作的绕过细节
重验触发器哪些变化会让旧证据失效应用、策略、模型或签名变化即重验旧版本结论不能迁移到新候选

事实依据与适用边界

以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。

本文判断事实或工程依据适用限制
Apple App Attest 的应用证明与断言需要由服务器验证,并关联到服务器关心的具体请求。Apple App Attest server validation该平台材料不证明用户拥有模型权益,也不保证设备或进程在所有时刻绝对可信。
Play Integrity 标准请求可使用 requestHash 将完整性响应绑定到稳定序列化后的业务请求,并由后端重算核对。Play Integrity standard requestsrequestHash 的正确性依赖双方采用同一规范化输入,完整性响应仍需结合业务授权规则。
Play Integrity 应作为整体反滥用策略中的一个信号,并靠近受保护操作由后端完成验证和决策。Play Integrity overview平台信号不证明 VMP 配置、Root 状态、模型保密性或任何攻击已经被阻断。
DPoP 可以将 OAuth 访问令牌绑定到客户端持有的密钥,降低被盗令牌在其他客户端重放的风险。RFC 9449 OAuth DPoPDPoP 不证明设备可信,也不能替代短有效期、受众、scope、撤销与最终业务授权。
OAuth 部署需要系统处理令牌重放、授权码注入、重定向和不安全授权模式等已知风险。RFC 9700 OAuth Security BCP该最佳实践不决定某个 AI 服务的套餐、模型版本、次数或地区权益。
更新客户端可以通过签名元数据、版本、过期时间和一致性关系识别回滚、冻结与混搭风险。The Update Framework specificationTUF 解决更新信任链问题,不直接定义移动端模型的商业权限或平台证明流程。
工程判断:挑战、证明、会话、权益和模型版本应共享可核对的请求标识,并在服务端以原子状态转换完成放行。工程判断:基于跨服务请求绑定、并发消费和最小授权原则具体字段、存储和有效窗口需要按项目架构与风险等级设计,不能照搬为通用合规结论。
当前没有具体候选包、真实平台证明回执、权益数据库与资源服务事件链,不能宣称模型重放或越权已经被阻断。项目证据尚未接入文章提供的是架构、代码检查器和验收方法,不包含客户案例、性能数字或产品实测结论。

工程常见问题

平台证明通过后,为什么还不能直接下发端侧模型?

平台证明主要提供应用实例和运行环境的风险信号,不回答当前用户是否购买模型、是否允许访问指定版本以及本次操作是否在有效范围。服务端仍需核对会话主体、模型权益、动作、受众、期限和产物身份,并把这些条件绑定到同一请求。

一次成功的 App Attest 或 Play Integrity 结果可以缓存多久?

没有适用于所有业务的固定时长。缓存窗口应按模型价值、操作风险、平台信号和可用性目标决定。高价值模型下载更适合让证明靠近当前请求;即使允许短期缓存,也要绑定应用实例、会话、模型动作与策略版本,撤销事件应优先于缓存。

DPoP 能否代替 App Attest 或 Play Integrity?

不能。DPoP 证明调用方持有与访问令牌绑定的密钥,重点降低令牌被复制后的重放;平台证明提供应用实例和平台环境相关信号。两者覆盖的威胁不同,还都不能替代用户会话、模型权益和资源服务的最终授权。

模型已经下载到设备后,服务端撤销权益还有意义吗?

有意义,但边界必须说清。撤销可以阻止后续下载、密钥获取、在线推理或周期性续期,却不能自动抹除设备上已经存在的明文或运行时数据。离线可用窗口越长,撤销生效越慢,业务需要显式接受并记录这部分剩余风险。

为什么模型授权必须精确到版本和摘要?

同名模型可能因量化方式、分词器、适配器或依赖变化成为不同产物。只授权产品名会让旧令牌覆盖未审核的新文件,也难以识别回滚与混搭。权益令牌、更新元数据和资源路径使用同一模型标识、版本与摘要,才能把商业权限落到具体交付对象。

怎样证明绑定流程已经正确实现?

需要固定候选身份和策略版本,保存挑战签发、平台验证、会话核对、权益匹配、产物确认、令牌签发与资源服务接受的连续事件,并执行请求标识替换、模型版本替换、错误受众、已消费挑战、过期与撤销等负向用例。只有真实回执闭合后才能登记项目通过。

想用自己的 App 验证?

提交候选包、目标系统和关键业务路径,申请御盾 PoC 与兼容性评估。

继续阅读: 移动 AI 应用的运行时安全边界