Table of contents
Open Table of contents
前言
企业应用通过网页或二维码进行 OTA(Over-the-Air)安装,是测试环境和企业内部分发中很常见的一条链路。正常情况下,用户扫描二维码后,系统会识别 itms-services 地址,下载 manifest,再根据 manifest 中的地址下载 IPA。
这条链路长期稳定时,一旦新系统上突然无法安装,排查方向很容易先落到二维码、签名、描述文件或 IPA 上。
这次遇到的现象比较特殊:
- iOS 26 及之前版本安装正常
- iOS 27 扫描相同二维码后没有弹出安装确认框
- App、IPA 和 manifest 均未主动调整
最终定位发现,问题不在 iOS 项目,而在 OTA 下载服务器的 TLS 配置。服务器虽然支持 TLS 1.2,但没有协商 Extended Master Secret(EMS),无法满足 iOS 27 对系统安装进程启用的 FCP v2.1 网络安全要求。
本文记录完整排查过程,并给出服务端修复与验收方法。文中的域名、路径和业务信息均已匿名化。
一、问题现象
测试环境生成企业签名的 IPA 和 manifest 后,通过二维码触发 OTA 安装:
itms-services://?action=download-manifest&url=https://download.example.com/ota/manifest.plist
在不同系统版本上的表现如下:
| 系统版本 | 表现 |
|---|---|
| iOS 26 及之前版本 | 扫码后正常弹出安装确认框 |
| iOS 27 | 扫码后停留在当前页面,不弹出确认框 |
表面上看像是二维码没有被识别,或者 itms-services 没有生效。但仅凭 UI 表现无法判断失败发生在哪一步,需要先把 OTA 安装链路拆开。
二、企业应用 OTA 安装链路
一次完整的 OTA 安装大致包含以下步骤:
扫描二维码或点击安装链接
↓
系统接收 itms-services 请求
↓
系统进程 appstored 下载 manifest
↓
解析 manifest 中的 metadata 和 software-package
↓
从 software-package URL 下载 IPA
↓
校验签名、描述文件和设备权限
↓
弹出确认并完成安装
这里有一个容易忽略的关键点:
OTA 安装并不是由当前 App 的网络层完成,manifest 和 IPA 的请求由系统进程发起。
因此,App 自己能正常访问某个 HTTPS 地址,不代表系统安装进程也一定会接受该服务器的 TLS 配置。App 内的 Info.plist、ATS 白名单或网络库配置,也无法绕过系统进程的安全检查。
三、先排除几个常见误判
在拿到设备日志之前,下面几个方向都值得怀疑,但不能仅凭现象直接下结论。
3.1 二维码识别失败
如果系统日志已经出现 manifest 安装请求,说明二维码内容和 itms-services 已经被系统正确接收。此时继续修改二维码生成逻辑没有意义。
3.2 itms-services 在新系统中被废弃
Apple 的企业应用分发文档仍然使用 itms-services://?action=download-manifest&url=... 作为网页 OTA 安装入口。问题不能简单归因于协议被移除。
3.3 IPA 或扩展签名错误
签名、Provisioning Profile、Bundle Identifier 或 App Extension 配置错误,通常发生在 manifest 已经下载并解析、系统准备下载或安装 IPA 之后。
如果日志显示 manifest 请求在 TLS 握手阶段就失败,系统尚未拿到 manifest,更不可能进入 IPA 签名校验阶段。
3.4 App 自身的 ATS 配置错误
manifest 请求来自 appstored,而不是业务 App。修改业务 App 的 NSAppTransportSecurity 配置,无法改变系统进程采用的网络安全策略。
3.5 manifest 中的图标加载失败
图标地址错误可能影响安装界面展示,但不会解释 manifest 本身下载到 0 字节。如果主 manifest 请求已经失败,应先解决 TLS 连接问题。
四、通过设备日志确定失败位置
这次排查最关键的一步,是确认系统究竟有没有收到安装请求,以及请求停在了哪一层。
4.1 系统已经接收安装请求
设备日志中可以看到类似信息:
Received request: ASDExternalManifestRequest
Manifest for UPP: https://download.example.com/ota/manifest.plist
Downloading requested manifest at URL: https://download.example.com/ota/manifest.plist
这几行日志说明:
- 二维码内容已经被识别
itms-services请求已经进入系统安装流程appstored正在尝试下载 manifest
因此,问题已经从“为什么扫码没有反应”收敛为“为什么系统无法下载 manifest”。
4.2 TLS 握手被 FCP v2.1 阻断
继续查看网络日志,可以看到真正的失败原因:
Error [ATS FCPv2.1 violation]: TLS 1.2 negotiated without extended master secret (EMS) for server: redacted
底层 TLS 库同时报告缺少必要扩展:
SSL routines:OPENSSL_internal:MISSING_EXTENSION
服务器和设备成功协商到了 TLS 1.2,但没有协商 EMS。对旧系统来说,这套连接可能仍然可以使用;对 iOS 27 的企业应用安装进程来说,它已经不满足 FCP v2.1 要求。
4.3 manifest 实际下载了 0 字节
最终的请求结果进一步确认失败发生在内容下载之前:
HTTP load failed, 0/0 bytes (error code: -1200 [3:-9880])
UPPManifestDownloadTask completing with error: NSURLErrorDomain Code=-1200
NSURLErrorDomain Code=-1200 表示无法建立安全连接。结合前面的 FCP v2.1 日志,可以得到完整证据链:
系统接收 itms-services 请求
↓
appstored 准备下载 manifest
↓
连接 OTA 文件服务器
↓
协商 TLS 1.2,但未协商 EMS
↓
iOS 27 判定为 ATS FCP v2.1 违规
↓
TLS 握手终止,manifest 下载 0 字节
↓
系统无法进入安装确认流程
五、使用 nscurl 复核服务器配置
设备日志已经给出了明确方向,接下来可以在 Mac 上使用 nscurl 对实际 manifest 地址执行 ATS 诊断:
nscurl --ats-diagnostics 'https://download.example.com/ota/manifest.plist'bash
失败服务器可能得到类似结果:
Configuring NIAP TLS package version requirements
FCP_v2.1
Result : FAIL
recommended
Result : FAIL
none
Result : PASS
这个结果很有价值:
none为PASS,说明在放宽安全限制时,普通 HTTPS 连接仍可能成功FCP_v2.1为FAIL,说明连接无法满足 iOS 27 系统安装流程采用的严格要求
因此,浏览器能打开 manifest、curl 能下载文件,或者旧版本 iOS 能安装,都不能证明该服务器已经满足 iOS 27 的企业应用安装要求。
六、根因:TLS 1.2 缺少 EMS
EMS 全称 Extended Master Secret,定义在 RFC 7627 中。它通过把主密钥与当前 TLS 握手绑定,降低特定类型的握手与会话攻击风险。
Apple 对 27.0 及后续系统中的部分系统进程启用了更严格的网络安全要求,覆盖范围包括:
- MDM 与声明式设备管理
- 自动设备注册
- 配置描述文件安装
- 企业应用安装
- 系统软件更新
对于只支持 TLS 1.2 的服务器,Apple 要求其配置至少满足相应的密码套件和扩展要求,其中包括 EMS。Apple 给出的修复方向也很明确:
- 优先升级为 TLS 1.3
- 如果继续使用 TLS 1.2,必须支持并协商 EMS
所以这次问题的本质不是“服务器不支持 HTTPS”,而是:
服务器提供的 TLS 1.2 已经不足以满足 iOS 27 企业应用安装进程执行的 FCP v2.1 安全要求。
七、为什么旧系统正常,iOS 27 才失败
旧系统能够安装,只能说明服务器满足旧系统当时执行的安全策略。
iOS 27 开始,Apple 对直接参与企业应用安装的系统网络连接执行更严格的 TLS 校验。不合规连接不再只是记录警告,而是会被系统阻断。因此,同一个 manifest 地址可能出现以下差异:
iOS 26:TLS 连接成功 → 下载 manifest → 进入安装流程
iOS 27:FCP v2.1 校验失败 → TLS 握手终止 → 无法下载 manifest
这也解释了为什么 App 没有改代码、IPA 没有改签名,问题仍然会随着系统升级出现。
八、检查真正参与下载的服务入口
实际项目中,上传接口与文件下载地址经常不是同一个服务入口。例如:
上传接口:https://upload.example.com:9443/ota
下载地址:https://download.example.com/ota/manifest.plist
它们可能经过不同的 Nginx、负载均衡器、CDN、网关或 TLS 终止节点。即使上传端口通过了 FCP v2.1 检查,也不能证明设备最终访问的下载入口符合要求。
排查时必须对以下最终 URL 分别执行检查:
itms-services中的 manifest URL- manifest 中
software-package对应的 IPA URL - 中间发生跳转时的每一个 HTTPS 入口
不要只检查域名,也不要只检查上传服务。端口、CDN 节点和重定向目标发生变化,都可能对应完全不同的 TLS 配置。
九、服务端解决方案
修复目标是设备实际连接的 manifest 和 IPA 下载入口,而不是 iOS 项目代码。
9.1 优先启用 TLS 1.3
TLS 1.3 原生采用更现代的握手和密码套件,可以避开 TLS 1.2 缺少 EMS 的问题,也是 Apple 推荐的方向。
9.2 继续使用 TLS 1.2 时启用 EMS
如果由于兼容性或基础设施限制仍需保留 TLS 1.2,需要确保最终 TLS 终止节点支持并实际协商 Extended Master Secret。
仅修改表层 Web Server 配置不一定有效。应检查真正参与握手的组件,包括:
- Nginx 或 Apache
- OpenSSL 或其他 TLS 库
- 云负载均衡器
- CDN
- API 网关或反向代理
- 内外网之间的安全代理
较旧的服务组件或 TLS 库可能不支持 EMS,或者虽然具备能力,但对应配置并未在实际入口生效。
9.3 保证证书和密码套件满足 ATS
除 EMS 外,还应同时确认:
- TLS 版本不低于 1.2
- 使用支持前向保密的密码套件
- 证书链完整并受系统信任
- RSA、ECDSA 密钥长度符合 ATS 最低要求
- 证书签名算法符合当前安全要求
- 最终地址没有重定向到 HTTP
修复单个 EMS 错误后,如果服务器仍有其他 ATS 违规项,安装流程依然可能失败。
十、修复后的验收方法
服务端完成调整后,建议按 TLS、manifest、IPA 和真机四个层次验收。
10.1 TLS 验收
对 manifest 的最终地址执行:
nscurl --ats-diagnostics 'https://download.example.com/ota/manifest.plist'bash
至少需要看到:
FCP_v2.1
Result : PASS
如果 manifest 或 IPA 发生重定向,还需要对跳转后的最终地址重复检查。
10.2 manifest 验收
下载并检查 manifest:
curl -L --fail --output /tmp/ota-manifest.plist \
'https://download.example.com/ota/manifest.plist'
plutil -lint /tmp/ota-manifest.plistbash
验收标准:
- HTTP 状态码为
200 - 最终 URL 始终使用 HTTPS
- manifest 可以被
plutil正常解析 .plist推荐返回text/xml或application/xmlsoftware-package地址准确且可以从设备网络访问
10.3 IPA 地址验收
从 manifest 中找到 software-package URL:
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://download.example.com/ota/app.ipa</string>xml
然后对 IPA 地址执行同样的 ATS 诊断:
nscurl --ats-diagnostics 'https://download.example.com/ota/app.ipa'bash
IPA 地址同样需要在 FCP v2.1 模式下得到 PASS,响应类型通常使用 application/octet-stream。
10.4 真机验收
最后回到真实安装流程:
- 使用 iOS 27 真机扫描二维码
- 确认系统正常弹出安装确认框
- 确认 IPA 可以下载并完成安装
- 启动 App,验证企业证书和描述文件正常
- 使用 iOS 26 真机执行一次回归安装
nscurl 通过只能证明单个服务入口满足网络要求,不能代替完整的 OTA 安装验收。
十一、排查清单
以后遇到“扫码后没有弹出安装框”时,可以按下面的顺序排查:
- 确认二维码中是否为正确的
itms-services地址 - 从设备日志确认系统是否收到
ASDExternalManifestRequest - 确认失败发生在 manifest、IPA 下载还是签名安装阶段
- 搜索
ATS Violation、ATS FCPv2.1 violation和NSURLErrorDomain - 对 manifest 最终 URL 执行
nscurl --ats-diagnostics - 对 IPA 最终 URL 执行相同检查
- 检查重定向、端口、CDN 和负载均衡器是否改变了 TLS 终止节点
- 修复后分别验证 FCP v2.1、文件可访问性和真机安装
这个顺序的核心是先确定失败层级,再检查对应组件,避免在系统尚未下载 manifest 时反复修改 IPA 签名或 App 配置。
总结
这次问题最容易误导人的地方,是旧系统可以安装,并且浏览器也可能正常访问 manifest。真正决定 iOS 27 企业应用 OTA 安装能否继续的,不是“地址能不能打开”,而是系统安装进程是否接受服务器的 TLS 配置。
完整根因可以归纳为:
OTA 下载入口协商 TLS 1.2
+
未协商 Extended Master Secret
+
iOS 27 对企业应用安装执行 FCP v2.1
=
系统终止 TLS 握手,manifest 无法下载
解决方案不在 iOS 工程内部,而在服务端:优先升级 TLS 1.3;如果必须保留 TLS 1.2,则确保最终 TLS 终止节点支持并协商 EMS,同时满足其他 ATS 要求。
面对系统升级后才出现的网络问题,与其从 UI 表现猜测原因,不如先抓取系统日志,找到真正发起请求的进程和失败层级,再用 nscurl 对最终服务入口做定向验证。证据链一旦完整,问题通常会从“新系统无法安装”快速收敛为一个明确的服务端配置项。