先看结论与判断条件

  • 退出能力应在采购和首次接入时建立,至少保留企业可独立重建的原始发布基线、配置目标、签名控制、验证用例和责任人。
  • 可迁移的是业务资产、威胁、控制目标、构建输入、测试断言和证据索引;供应商专有变换、运行时协议和配置名称不能假设一一对应。
  • Android 应用签名与 Apple 代码签名采用不同身份链和发布流程,必须分别证明企业可持续更新,不能用同一证书字段混为一套回执。
  • 旧供应商的保护包不能替代未保护基线;生产迁移应从企业控制的源码、依赖、工具链和签名前产物重新构建新候选。
  • 新供应商候选需要重新完成保护验证、兼容矩阵、签名身份、更新路径和渠道交付,旧供应商报告只能作为需求与历史证据。
  • 退出关闭包含数据清单、删除范围、删除回执、账号与令牌撤销、构建访问复核、残余副本说明和责任人签收,不是停止续费即结束。

把可退出能力做成接入条件,而不是临时补救

供应商退出并不只发生在服务到期。价格、服务质量、地域合规、产品路线、企业并购、构建平台变化或关键人员调整,都可能触发迁移。真正的可退出能力,是企业在没有旧供应商继续配合的情况下,仍能从受控源码构建合法发布候选、维持应用身份、执行回归并安全更新用户,而不是合同里只有一句“支持迁移”。

退出计划应在首次接入时确定 owner、触发条件、通知期、交付物、数据处理、过渡支持和验收标准。技术 owner 管理基线与配置目标,安全 owner 管理证据与残余风险,密钥 owner 管理签名,发布 owner 管理渠道和更新身份,法务或采购 owner 管理数据删除与合同义务。没有责任人的清单在真正退出时很难执行。

企业还要定义不可接受的锁定。例如只能由供应商云端读取的配置、无法导出的保护映射、供应商代持且企业无法恢复的发布身份、只在对方环境可运行的测试,以及没有源码基线的二进制交付。并非所有专有实现都必须开放,但企业要知道哪些能力不能迁移,并为这些差异准备重建和重验预算。

退出能力的技术、组织与证据要素
要素企业必须控制供应商可协助退出验收
源码与依赖仓库、提交、锁文件和构建说明兼容建议和适配补丁无供应商环境可构建基线
签名身份密钥策略、证书、恢复和轮换责任按最小权限完成签名接口企业可持续合法更新
加固目标资产、威胁、控制目标和范围专有能力配置与实现新方案可重新映射并重验
验证用例业务断言、设备矩阵和接受标准供应商专用诊断工具不依赖旧平台也能执行关键回归
构建证据产物摘要、provenance 和审批索引生成阶段日志和供应商声明每份证据绑定明确候选
数据与访问数据清单、授权、撤销和保留策略删除回执与过渡支持访问失效、数据处置有证据

区分可迁移资产与供应商专有实现

退出盘点应把资产分为企业原始资产、通用安全目标、供应商生成资产和供应商运营数据。源码、资源、签名前产物、业务测试、威胁模型和签名身份属于企业必须控制的基线;“保护离线授权决策”“检测关键资源异常”属于可重新映射的控制目标;虚拟化指令、运行时协议、专用映射和内部规则通常属于专有实现。

配置迁移不应试图把旧供应商的每个开关翻译成新供应商同名开关。名称相同也可能改变作用域、兼容限制和运行时行为,名称不同也可能服务同一控制目标。更稳妥的中间格式是 asset_id、threat_id、control_objective、protected_scope、compatibility_boundary 和 verification_case,新方案根据这些目标设计配置,再用候选回执验证。

供应商运营数据包括上传的 APK、AAB、IPA、符号、映射、诊断日志、设备回执、配置快照、账号信息和支持工单附件。退出清单要写明每类数据的所有者、存储位置、处理依据、保留期、子处理方和删除方式。不能因为门户里看不到文件就认为副本已删除,备份和支持系统中的残余范围也应由回执说明。

  • 企业源码、资源、依赖和签名前基线位置明确
  • 控制目标使用资产与威胁语言而非专有开关名
  • 供应商生成包、映射、日志和配置分别登记
  • 专有实现无法迁移的范围与替代成本已知
  • 所有供应商数据标注存储、保留和删除责任
  • 子处理方与备份副本进入退出数据清单

Android 与 Apple 签名身份必须分别保全

Android 使用应用签名建立安装和更新身份,不同签名方案覆盖的文件区域与平台版本有所不同。企业需要保存应用签名或商店托管责任、上传密钥、证书摘要、轮换与恢复流程,并明确供应商是否曾接触签名材料。旧供应商生成的 APK 若使用临时证书,不能作为生产更新基线;签名有效也只证明完整性和签名者身份。

Apple 平台通过代码签名、证书与运行验证建立 App 身份,相关流程与 Android APK 签名不能合并成一个 certificate 字段。退出计划应分别记录 Apple 团队责任、证书和配置文件管理、构建与签名阶段、App 标识以及发布渠道。供应商若代执行签名,企业仍需能在自有授权链中重建并验证最终候选。

迁移中最危险的动作是为了适配新工具而重签现有生产包、重新包装旧保护包或替换内部文件。任何字节变化都会产生新候选,并可能影响签名与测试关系。正确流程是从企业控制的原始源码和可发布基线重新构建,再由当前签名链签发;旧保护包仅用于历史对照,不能逆向恢复成可信基线。

两类移动平台签名退出清单
平台对象企业控制项迁移验证禁止替代
Android 应用身份applicationId、应用签名证书和更新责任签名验证、安装更新和渠道身份供应商临时证书
Android 上传身份上传密钥、轮换与商店托管关系上传授权和返回渠道回执把上传密钥当应用签名密钥
Android 签名方案目标平台和签名参数最终 APK 的方案与证书回执只检查文件扩展名
Apple 应用身份团队、App 标识、证书与发布授权最终归档、签名和渠道验证沿用 Android 证书字段
Apple 配置与能力entitlement、profile 和服务配置责任目标能力、安装和运行回归只看签名命令成功
共同证据候选摘要、签发者、时间和审批与测试和发布回执绑定用旧包报告证明新包身份

保留可独立重建的原始发布基线

可退出基线不是旧供应商处理后的最终包,而是企业能独立重建的原始发布输入:源码提交、依赖锁、构建工具、变体、资源、平台 SDK、签名前产物生成步骤和预期摘要。若只有保护包,没有可构建源码和依赖,迁移只能从不完整二进制猜测,既不可靠,也难以证明业务语义与发布身份。

SLSA Provenance v1.1 可以记录产物 subject、builder、buildType、externalParameters 与 resolvedDependencies,适合说明基线和新候选如何产生。加固供应商、配置摘要与工具版本可以作为构建参数或材料进入证明。provenance 只能证明记录的构建关系,不证明运行时安全,也不能弥补缺失的源码、密钥或兼容回执。

in-toto Attestation Statement v1 可把产物摘要与有类型的声明负载绑定。退出时可以用它索引基线、供应商输入输出、测试和删除回执,避免把一份报告贴到另一个候选。声明格式本身不保证内容真实,企业仍需验证签发者、签名、subject 和 predicate,并确保停止合作后仍能读取必要证据。

  • 源码提交、依赖锁、工具链和变体能够独立复现
  • 签名前基线与最终候选分别计算摘要
  • 供应商输入、输出和配置摘要进入 provenance
  • 测试、签名和发布声明绑定同一 subject
  • 退出后证据索引仍可由企业读取和验证
  • 旧保护包不会被当成原始源码或可发布基线

用验证用例迁移安全目标,不迁移旧结论

供应商迁移真正可复用的是验证问题。例如高价值授权路径是否仍由服务端最终决定,目标结构在静态检查中暴露到什么程度,完整性异常如何处置,关键 Native 路径在目标 ABI 是否保持业务语义。这些用例与业务资产绑定,可以交给新方案重跑;旧供应商“已通过”的结论则只属于旧候选。

新候选至少需要保护验证与兼容验证两组回执。保护验证检查新方案是否覆盖目标资产和威胁,兼容验证检查安装、启动、登录、支付、数据、后台任务、异常路径和设备矩阵。即使新供应商声称功能等价,配置作用域、代码变换和运行时依赖仍会变化,不能承诺无损替换或复用旧设备结果。

迁移还要保留原始对照与旧供应商最终稳定候选。原始对照帮助区分应用本身问题,旧稳定候选帮助观察行为差异,新供应商候选用于验收目标控制。三者都要绑定来源、签名和摘要,不能重命名文件后比较。若旧候选无法在当前渠道重新发布,也要明确它仅是诊断对照而非可回滚包。

哪些材料可迁移,哪些结论必须重建
材料可迁移部分必须重建部分主要原因
威胁模型资产、威胁、业务影响和控制目标新控制映射与残余风险实现与边界变化
配置保护范围、兼容约束和验证目标新供应商具体参数开关语义不等价
静态验证检查方法、样本和接受标准新候选观察与回执二进制结构不同
设备回归用例、数据、矩阵选择原则新候选每台设备结果运行时和产物变化
供应商报告历史问题、范围和证据索引当前保护与兼容结论报告只绑定旧候选
回滚材料决策条件和渠道流程合法可更新候选与签名回执旧包未必可重新发布

分阶段切换构建、发布与数据责任

执行迁移可以分为冻结、复现、映射、PoC、生产接入、受控发布和关闭七个状态。冻结旧稳定候选与证据;复现企业原始基线;把控制目标映射到新方案;对新候选做 PoC;接入生产签名与 CI;在批准范围内发布;最后完成数据删除与权限撤销。每个状态都应有 owner、输入、退出条件和不可变回执。

更新系统还要防止回滚、冻结和混搭风险。The Update Framework specification 通过签名角色、版本、过期时间和一致快照处理更新元数据风险,可以作为企业自管更新服务的参考,但它不直接规定 APK、IPA 或业务授权。应用商店和企业分发各有身份与版本规则,迁移方案必须按实际渠道验证,不能简单套用 TUF 名称。

受控发布前要定义停止推进、恢复业务开关和发布修复候选的条件。旧候选能否回滚取决于签名、版本、渠道和已安装用户状态,不能默认随时降级。更可靠的做法是准备经过验证的前向修复路径,并保留旧供应商配置与新配置的差异记录。任何紧急包也要进入合法签名链和最小门禁。

  • 冻结、复现、映射、PoC、生产、发布和关闭状态明确
  • 每个状态具有 owner、输入、门禁和回执
  • 更新元数据验证与移动应用发布规则分别处理
  • 停止推进、业务降级和前向修复条件预先定义
  • 旧候选是否可回滚经过签名与渠道核对
  • 紧急候选仍使用合法身份和最低验证门禁

用只读脚本检查退出清单是否具备闭环

下面的 Python 脚本读取脱敏退出清单,逐个平台检查责任人、签名控制、原始基线、provenance subject、验证用例、供应商数据清单、删除回执、访问撤销和回滚说明。它不接触密钥、不删除数据、不撤销账号,只把缺失字段列入 BLOCK,适合在退出评审前做结构门禁。

签名记录只保存证书摘要、custodian 和 recovery_process,不应包含私钥。baseline 要同时保存 artifact_sha256、source_sha256、dependency_lock_sha256 和 provenance_subject_sha256,脚本要求证明 subject 与基线产物一致。删除回执需要 receipt_id、scope、issuer 和 owner,访问撤销需要 receipt_id 与 owner,避免用口头确认关闭。

清单完整不证明供应商真的删除了所有副本,也不证明新候选可发布。法务、采购、安全、密钥 owner 和发布 owner 仍需核对合同、子处理方、备份残余、签名权限和真实渠道回执。脚本的目的只是阻止明显遗漏,让每个退出结论有责任人和可追踪证据引用。

校验加固供应商退出清单的关键证据
import json
import sys
from pathlib import Path

REQUIRED_PLATFORM = {
    "platform",
    "owner",
    "signing_control",
    "baseline",
    "verification_cases",
    "vendor_data_inventory",
    "deletion_receipt",
    "access_revocation",
    "rollback_plan",
}
HASH_FIELDS = {"artifact_sha256", "source_sha256", "dependency_lock_sha256", "provenance_subject_sha256"}

def stop(message):
    raise SystemExit(f"invalid exit plan: {message}")

def nonempty(value):
    return isinstance(value, str) and bool(value.strip())

def require_receipt(value, name):
    if not isinstance(value, dict):
        stop(f"{name} must be an object")
    for field in ("receipt_id", "owner", "scope"):
        if not nonempty(value.get(field)):
            stop(f"{name}.{field} is required")

if len(sys.argv) != 2:
    raise SystemExit("usage: python exit_plan_gate.py PLAN_JSON")
plan_path = Path(sys.argv[1]).resolve()
if not plan_path.is_file():
    stop(f"file not found: {plan_path}")
payload = json.loads(plan_path.read_text(encoding="utf-8"))
platforms = payload.get("platforms")
if not isinstance(platforms, list) or not platforms:
    stop("platforms must be a non-empty list")
review = []
seen = set()
for index, item in enumerate(platforms):
    if not isinstance(item, dict):
        stop(f"platform {index} must be an object")
    missing = sorted(REQUIRED_PLATFORM.difference(item))
    if missing:
        stop(f"platform {index} missing {missing}")
    platform = item["platform"]
    if not nonempty(platform) or platform in seen or not nonempty(item["owner"]):
        stop(f"platform {index} has invalid identity or owner")
    seen.add(platform)
    signing = item["signing_control"]
    if not isinstance(signing, dict) or not nonempty(signing.get("certificate_sha256")) or not nonempty(signing.get("custodian")) or not nonempty(signing.get("recovery_process")):
        stop(f"platform {platform} lacks signing control")
    baseline = item["baseline"]
    if not isinstance(baseline, dict):
        stop(f"platform {platform} lacks baseline")
    for field in HASH_FIELDS:
        value = baseline.get(field)
        if not isinstance(value, str) or len(value) != 64:
            stop(f"platform {platform} has invalid {field}")
    if baseline["artifact_sha256"] != baseline["provenance_subject_sha256"]:
        stop(f"platform {platform} provenance subject mismatch")
    cases = item["verification_cases"]
    inventory = item["vendor_data_inventory"]
    if not isinstance(cases, list) or not cases or not all(nonempty(case) for case in cases):
        review.append({"platform": platform, "gap": "verification_cases"})
    if not isinstance(inventory, list) or not inventory or not all(nonempty(entry) for entry in inventory):
        review.append({"platform": platform, "gap": "vendor_data_inventory"})
    require_receipt(item["deletion_receipt"], f"{platform}.deletion_receipt")
    require_receipt(item["access_revocation"], f"{platform}.access_revocation")
    if not isinstance(item["rollback_plan"], dict) or not nonempty(item["rollback_plan"].get("owner")) or not nonempty(item["rollback_plan"].get("scope")):
        review.append({"platform": platform, "gap": "rollback_plan"})
output = {
    "plan_id": payload.get("plan_id"),
    "platform_count": len(platforms),
    "review": review,
    "gate": "BLOCK" if review else "READY_FOR_EXIT_REVIEW",
}
print(json.dumps(output, ensure_ascii=False, indent=2, sort_keys=True))

退出验收要同时关闭技术、数据和合同责任

退出完成的技术证据包括企业可独立构建的基线、新供应商最终候选、正确签名身份、保护与兼容回执、更新渠道验证、差异清单和前向修复方案。旧供应商稳定候选与报告可以保留为历史证据,但要明确其可用期限、读取方式和是否仍依赖供应商服务。

数据与访问关闭包括所有账号、API 凭据、CI 访问、仓库权限、上传入口、支持门户和远程诊断通道的撤销回执,以及 APK、IPA、AAB、符号、映射、日志和工单附件的删除或合法保留说明。删除回执只能证明声明的范围,备份、法规保留和子处理方残余应单独写明。

NIST SP 800-218 SSDF 强调持续维护、变更和供应链风险管理,退出方案也应进入日常演练,而不是只在争议时打开。需要评估实际迁移时,可通过御盾中央平台提交脱敏基线摘要、签名责任、控制目标、验证矩阵、供应商数据清单和渠道约束,先识别不可迁移项,再安排新候选和退出门禁。

  • 企业在退出后仍可独立构建、签名、测试和发布
  • 新候选取得保护、兼容、更新与渠道回执
  • 旧供应商数据删除范围和残余副本已说明
  • 所有账号、令牌、仓库与 CI 访问均有撤销回执
  • 专有资产、历史证据和支持期限进入合同关闭
  • 退出方案定期演练并随平台与供应链变化更新

事实依据与适用边界

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

本文判断事实或工程依据适用限制
更新系统应验证签名、版本、过期时间和一致快照,以识别回滚、冻结与混搭风险。The Update Framework specification 定义角色签名、版本、过期和一致快照等更新元数据机制。TUF 是更新框架,不直接规定 Android APK、Apple App 或业务授权策略,实际渠道仍需单独验证。
构建证明应绑定产物主体、构建者、构建类型、外部参数和依赖材料。SLSA Provenance v1.1 定义 subject、builder、buildType、externalParameters 和 resolvedDependencies 等字段。provenance 只能证明记录的构建关系,不能单独证明保护强度、运行时安全或迁移兼容。
供应链声明应把产物摘要与有类型的声明负载绑定。in-toto Attestation Statement v1 使用 subject 摘要、predicateType 和 predicate 组织证明。声明格式不保证签发者可信或内容真实,仍需验证签名、subject 和具体门禁。
Android 使用应用签名建立更新身份,不同签名方案覆盖不同文件区域与平台版本。AOSP app signing 说明 APK 签名用途、验证关系及各签名方案的适用范围。签名有效只证明完整性与签名者身份,不证明业务代码安全或新供应商候选兼容。
Apple 平台通过代码签名、证书与运行验证建立 App 身份。Apple app code signing process 说明 Apple 平台应用代码签名和运行验证关系。Apple 签名链与 Android APK 签名不能混为同一证据,文档也不证明实际候选签名正确。
安全发布应保留来源、构建、验证和变更证据,并管理供应链风险。NIST SP 800-218 SSDF 把来源追溯、构建完整性、验证、维护和供应链实践纳入安全开发。SSDF 是组织级框架,不定义某个 App 加固产品能力或供应商迁移的具体技术方案。
供应商迁移应复用业务资产、控制目标和验证用例,而不是假设专有配置无损转换。工程判断:不同供应商的变换、运行时协议、默认值和兼容边界可能不同,同名开关不代表等价。没有新候选和项目实测时,不能声明保护范围、强度、兼容性或迁移成本与旧方案相同。
退出关闭需要数据删除、访问撤销、基线控制和责任人回执共同完成。工程判断:停止续费不会自动删除供应商持有的产物和日志,也不会撤销仓库、CI、签名或支持访问。删除回执只覆盖声明范围,备份、法规保留、子处理方和合同义务仍需法务与安全核对。

工程常见问题

旧供应商的加固包可以直接交给新供应商继续处理吗?

不应把旧保护包当原始基线。应从企业控制的源码、依赖和可发布构建重新生成新候选,并重新完成签名与验证。

两家供应商都有同名功能,配置能直接一一转换吗?

不能假设等价。应迁移资产、威胁、控制目标和验证用例,由新方案重新映射配置并用候选回执验收。

更换加固供应商会影响应用签名吗?

供应商变化不应改变企业发布身份,但工具接入和签名阶段可能变化。Android 与 Apple 签名责任需分别核对和回归。

保留旧供应商测试报告能否减少新方案回归?

报告可复用范围、问题和用例,但结论只绑定旧候选。新二进制仍需重新取得保护、兼容、签名和渠道回执。

收到数据删除邮件是否就完成供应商退出?

不够。还要核对删除范围、备份与子处理方残余,并取得账号、令牌、仓库、CI、签名和支持通道的撤销回执。

准备供应商迁移评审需要哪些材料?

准备可构建基线、签名责任、控制目标、旧新候选摘要、验证矩阵、数据清单、访问清单、删除回执和发布约束。

想用自己的 App 验证?

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

继续阅读: 御盾 VMP 接入前准备清单