iOS 测试包装不上的时候,报错很少直接告诉你签名的哪一环出了问题。“无法安装”“The executable was signed with invalid entitlements”(0xe8008016)“A valid provisioning profile for this executable was not found”(0xe8008015),归根到底都落在两处的几项信息上:描述文件,以及可执行文件的代码签名。在 Mac 上一般这样看:
security cms -D -i embedded.mobileprovision
codesign -d --entitlements :- Payload/MyApp.app
这个页面直接从文件里读出同样的数据,在浏览器里完成,并把两边并排对照。它面向的是检查自己的包和描述文件:测试机为什么装不上、导出的 IPA 里到底塞的是哪个描述文件、推送 entitlement 写的是 development 还是 production。
使用步骤
- 准备要检查的包或描述文件:
.ipa、压缩过的.app或.xcarchive,或者.mobileprovision文件(Xcode 下载的描述文件在~/Library/MobileDevice/Provisioning Profiles/里)。 - 把它拖到上方的拖放区,或者点“选择文件”。文件在浏览器里读取,不会上传。
- 先看“概览”里的检查结果(有效期、Bundle ID、entitlements、团队、签名证书),再看描述文件的类型和设备列表,以及“代码签名里的 entitlements”表:每一项签入的值旁边就是描述文件允许的值。
真实输出示例
我们把本站测试用例里的 IPA 样本拖进本页,把页面显示的结果复制下来。排版做了压缩(标签和值放在同一行,省去应用信息和 Info.plist 部分),所有值都原样保留。剩余天数按抓取当天(2026-11-02)计算。
样本: 本站代码仓库里的 test/ios/good.ipa:由 test/ios/build.sh 用一次性的 OpenSSL CA 生成的合成 IPA。团队 FIXTEAM123、各个 UDID 和全部证书都是虚构的,不涉及任何真实的 Apple 描述文件。
概览
文件类型 IPA
文件大小 20.8 KB
正常 描述文件有效期到 2036-01-01(还剩 3346 天)。
正常 Bundle ID com.example.fixture 与描述文件的 App ID FIXTEAM123.com.example.fixture 匹配。
正常 可执行文件签入的每一项 entitlement 都在描述文件允许的范围内。
正常 团队 FIXTEAM123 与描述文件的团队一致。
正常 签名证书(Apple Development: Fixture Developer (FIXDEV0001))在描述文件的开发者证书列表里。
正常 每个 App 扩展的 Bundle ID 都以 com.example.fixture 开头。
描述文件
名称 Fixture Dev Profile
描述文件类型 开发(DEVELOPMENT) 列有设备,且允许 get-task-allow(可挂调试器)
App ID(application-identifier) FIXTEAM123.com.example.fixture
UUID F7021ABE-0000-4000-8000-000000000001
团队 FIXTEAM123 Fixture Org
平台 iOS
过期时间 2036-01-01 00:00:00 UTC 还剩 3346 天
已注册设备(3)
00008110-000A1B2C3D4E5F01
00008120-000A1B2C3D4E5F02
0123456789abcdef0123456789abcdef01234567
开发者证书(1)
Apple Development: Fixture Developer (FIXDEV0001) FIXTEAM123 AB:65:6E:F8:2E:D5:84:B2:7A:D2:03:4F:5E:39:27:35:75:82:9C:CD
…
主可执行文件与代码签名
架构 平台 最低系统版本 LC_ENCRYPTION_INFO
arm64 iOS 15.0, SDK 27.0 cryptid 0
arm64e iOS 15.0, SDK 27.0 cryptid 0
…
代码签名里的 entitlements
键 签入的值 描述文件允许 状态
application-identifier FIXTEAM123.com.example.fixture FIXTEAM123.com.example.fixture 允许
aps-environment development development 允许
com.apple.developer.associated-domains applinks:example.test * 允许
com.apple.developer.team-identifier FIXTEAM123 FIXTEAM123 允许
get-task-allow true true 允许
keychain-access-groups FIXTEAM123.com.example.fixture FIXTEAM123.* com.apple.token 允许可以拖进来的文件
- 描述文件:
.mobileprovision(iOS、iPadOS、tvOS、watchOS、visionOS)和 macOS 的.provisionprofile。描述文件是一个 CMS SignedData,里面包着 XML plist;现在的描述文件用定长编码,早期的描述文件和openssl cms -stream生成的是 BER 不定长编码,两种都能读。security cms -D输出的 XML 也可以直接拖进来。 - 安装包:
.ipa、压缩过的.app(访达里的“压缩”)或压缩过的.xcarchive。先只读 ZIP 中央目录,然后只打开 App 的Info.plist、embedded.mobileprovision、PrivacyInfo.xcprivacy、各扩展的Info.plist和主可执行文件。几个 GB 的 IPA 也不会整个读进内存,支持 ZIP64。 - 单个文件:XML 或二进制(
bplist00)格式的Info.plist、.entitlements、PrivacyInfo.xcprivacy,或者一个单独的 Mach-O 可执行文件(单架构或通用二进制),用来列出签名里的 entitlements。
能看到什么
描述文件:名称、App ID 名称、App ID(application-identifier,带团队前缀)、UUID、团队 ID 和名称、平台、创建和过期时间(以及还剩几天)、是否由 Xcode 管理、完整的 Entitlements 字典、注册设备的 UDID 列表(带搜索框)、开发者证书(通用名、取自 OU 字段的团队、SHA-1、有效期),以及 CMS 签名者的证书。
描述文件类型并不写在文件里,这里按 Xcode 和 fastlane 的判断方式推出来:
| 描述文件包含 | 类型 |
|---|---|
ProvisionsAllDevices = true |
企业(In-House);macOS 上是 Developer ID |
ProvisionedDevices,且 get-task-allow = true |
开发(Development) |
ProvisionedDevices,get-task-allow = false |
Ad Hoc |
| 两者都没有 | App Store(macOS 上是 Mac App Store) |
App:Bundle ID、版本和 build 号、最低系统版本、SDK 和 Xcode 版本、设备类型、支持的平台、URL Scheme、必需的设备能力、App Transport Security 配置、每一条 NS…UsageDescription 及其文案、隐私清单(追踪开关、追踪域名、收集的数据类型,以及每个需要理由的 API 类别和理由代码,例如 CA92.1)、内嵌的框架和 dylib(标出自带隐私清单的那些),以及 App 扩展的 Bundle ID 和扩展点。
可执行文件:各架构切片(arm64、arm64e 等)及其 LC_BUILD_VERSION 里的平台和最低系统版本、LC_ENCRYPTION_INFO_64 的 cryptid(为 1 表示 App Store 下载的包,代码是加密的)、CodeDirectory 里的签名标识符和团队、是否为 ad hoc 签名、签名证书,以及代码签名里两份 entitlements:XML plist(slot 5)和 iOS 15 起实际校验的 DER 版本(slot 7)。两份不一致时页面会提示。
会做哪些检查
拖入的是带描述文件的包时,页面会把常见安装失败背后的几项逐一核对:
- 过期:描述文件已过期,或 30 天内过期。
- Bundle ID:
CFBundleIdentifier必须匹配描述文件 App ID 去掉团队前缀后的部分,*是通配符(TEAMID.com.example.*)。 - Entitlements:可执行文件签入的每一项 entitlement 都必须在描述文件允许的范围内。字符串和数组可以用描述文件里的通配符(钥匙串分组的
TEAMID.*、Associated Domains 的*);App Store 或 Ad Hoc 包里get-task-allow为 true、production描述文件配上development的aps-environment、别的团队的钥匙串分组、描述文件没有开通的能力(iCloud、App Groups),都会列出键名和两边的值。macOS 上com.apple.security.开头的沙盒和 Hardened Runtime 权限不需要描述文件,会单独标出。 - 团队:签入的
application-identifier(或 CodeDirectory)里的团队与描述文件的团队是否一致。 - 签名证书:代码签名里的证书必须在描述文件的
DeveloperCertificates里,按 SHA-1 比对。对应的就是“描述文件不包含签名证书”这种情况。 - 扩展:每个
.appex的 Bundle ID 都必须以主 App 的 Bundle ID 加一个点开头。
想确认某台设备在不在描述文件里,把它的 UDID 粘贴到设备列表上方的输入框。比对只在页面里进行,不会发送到任何地方。
我们是怎么验证的
测试样本都是合成的,由 test/ios/build.sh 在 macOS 27.0.1、Xcode 27.0(iPhoneOS 27.0 SDK)和 OpenSSL 4.0.3 上生成:一个一次性的 CA、一张“开发者”证书和一张描述文件签名证书;五个手写的描述文件(开发、BER 不定长编码的 Ad Hoc、已过期的 App Store、App ID 为 * 的企业版,以及一个 macOS 开发描述文件),用 openssl cms -sign -binary -nodetach 签名;再把一个很小的 C 程序编译成 arm64 和 arm64e,用 codesign --entitlements --generate-entitlement-der 签名后打包成 Payload/Fixture.app。另一个“错误”的包故意做错了一堆东西:Bundle ID 不对、对着 App Store 描述文件签了 get-task-allow true 和开发环境推送、用了别的团队的钥匙串分组、多了一个描述文件没有的 iCloud 权限、签名证书不在描述文件里、Widget 扩展的 Bundle ID 前缀不对,还有一个切片的 cryptid 被改成了 1。单元测试把解析结果和 security cms -D、plutil、codesign -d --entitlements(XML 和 --der 两种)、lipo -archs、otool -l 的输出逐项比对。我们还用解析器跑了一遍这台 Mac 上 Xcode 下载的 20 个真实描述文件:名称、UUID、团队、过期时间、设备列表、entitlements 和证书 SHA-1 全部 20 个都与 security cms -D 一致(这些数据不在本站和测试里出现)。
做样本时踩了两个坑。plutil -convert json 会拒绝解码后的描述文件,报 “Invalid object in plist for JSON format”,因为描述文件里有 JSON 表示不了的 <date> 和 <data>,所以构建脚本改用 plutil -p 生成参照输出。另外 codesign -dv 根本不打印 Authority= 行,要用 -dvvv 才能看到是哪张证书签的。如果把 security cms -D -i 直接用在 .ipa 上而不是里面的描述文件,会报 security: failed to add data to decoder: UNKNOWN (-8183(d):先解压,或者直接把 IPA 拖到这里。
你的文件如何保持私密
- 查看器运行在一个单独的框架(iframe)里,来源是
tools.rea-reverse.com;文件在框架里的 Web Worker 中、在你的设备上读取,不上传,这个工具也没有服务端。 - UDID、团队 ID、Bundle ID 和证书信息只显示在框架里。粘贴的 UDID 只在框架里比对,不保存。
- 这个页面在正文下方展示一个广告。广告脚本运行在
rea-reverse.com上,而框架属于另一个源(origin),浏览器不允许广告脚本或本页的任何其他脚本读取你拖入的文件、粘贴的 UDID 或读取结果。 - 框架的内容安全策略拦截它的脚本能发起的所有请求(
connect-src 'none',也不加载任何第三方资源)。框架也不能自行打开窗口或让本页跳转。它传给本页的只有自身高度和下面这些统计事件。 - 统计事件只含粗粒度的值:成功或错误码、文件类型(
profile、ipa等)、推出的描述文件类型、未通过的检查项数量区间,以及文件大小和耗时区间。框架把事件交给本页,由本页发送。完整列表见隐私政策。 - 不想要任何广告和统计?在单独页面打开查看器。
如何自己验证
- 打开浏览器开发者工具的 Network(网络) 面板,加载本页。发往
tools.rea-reverse.com的请求就是框架:它的 HTML、样式、字体和脚本,以及 Worker 脚本/_astro/ios-worker-*.js。发往 Google 域名的请求是本页的广告。 - 清空日志,然后拖入一个 IPA。在过滤框里输入
domain:tools.rea-reverse.com:最多只会再出现一次 Worker 脚本/_astro/ios-worker-*.js(为下一次运行准备的新 Worker),没有任何请求带着你的数据。广告请求可能还会继续,但它们来自本页,不带框架里的任何内容。 - 点开来自
tools.rea-reverse.com的ios-provisioning-profile-viewer/文档,查看 Headers:它的Content-Security-Policy里有connect-src 'none'和frame-ancestors https://rea-reverse.com。在 Chrome 里,Application(应用)> Frames 也能看到框架的源和策略。Worker 运行在default-src 'none'; script-src 'self'; connect-src 'none'这条策略下,浏览器会拦下它发出的任何请求。 - 检查隔离:在 Console(控制台) 里,上下文选
top,运行document.getElementById('tool-frame').contentDocument,结果是null;frames[0].document会抛出SecurityError。广告脚本拿到的也是这个结果。 - 或者先打开页面再断网,然后拖入一个描述文件,照样能用。
- 用你自己的工具对照:
security cms -D -i file.mobileprovision、codesign -d --entitlements :- MyApp.app和otool -l MyApp | grep -A4 LC_ENCRYPTION会给出同样的值。
局限
- 不做任何密码学校验。 既不校验 Apple 对描述文件的签名,也不校验 App 的代码签名;签名后被改过的描述文件或二进制在这里看着一切正常,但设备会拒绝。
- 不解密、不重签、不修改。App Store 下载的包只读取 load command 和签名。
- App 扩展只列出 Bundle ID,不交叉检查它们自己的描述文件和 entitlements。需要的话把扩展的
embedded.mobileprovision单独拖进来。 - Entitlements 的比对规则覆盖常见情况(精确值、
*通配符、数组取子集)。Apple 安装器还有一些特殊规则这里没有模拟,例如只授予 Apple 自己签名代码的权限。 - 设备列表来自描述文件本身。设备在开发者账号里是否仍处于启用状态,从文件里看不出来。
对应的命令行
在 Mac 上先解压 IPA(直接对 IPA 运行 security cms -D 会失败),再解码里面的描述文件,用 plutil 取字段(macOS 27.0.1):
$ unzip -q good.ipa && security cms -D -i Payload/Fixture.app/embedded.mobileprovision | plutil -extract ExpirationDate raw -o - -
2036-01-01T00:00:00Z$ security cms -D -i Payload/Fixture.app/embedded.mobileprovision | plutil -extract ProvisionedDevices json -o - -
["00008110-000A1B2C3D4E5F01","00008120-000A1B2C3D4E5F02","0123456789abcdef0123456789abcdef01234567"]可执行文件实际签入的 entitlements(描述文件必须允许这些值):
$ codesign -d --entitlements - --xml Payload/Fixture.app
Executable=…/Payload/Fixture.app/Fixture
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>application-identifier</key><string>FIXTEAM123.com.example.fixture</string><key>aps-environment</key><string>development</string><key>com.apple.developer.associated-domains</key><array><string>applinks:example.test</string></array><key>com.apple.developer.team-identifier</key><string>FIXTEAM123</string><key>get-task-allow</key><true/><key>keychain-access-groups</key><array><string>FIXTEAM123.com.example.fixture</string></array></dict></plist>没有 Mac 时,OpenSSL 打印的是同一份 plist(OpenSSL 4.0.3;-noverify 跳过证书链校验)。它的输出和 security cms -D 逐字节相同:
$ openssl cms -verify -noverify -inform DER -in Payload/Fixture.app/embedded.mobileprovision
CMS Verification successful
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AppIDName</key>
<string>Fixture & Friends</string>
…
<key>ExpirationDate</key>
<date>2036-01-01T00:00:00Z</date>
<key>Name</key>
<string>Fixture Dev Profile</string>
…常见问题
怎么解码 .mobileprovision 文件?
拖到这里,就能看到逐项排好的字段,描述文件类型、剩余天数和证书 SHA-1 也都算好了。在 Mac 上,security cms -D -i file.mobileprovision 打印里面的 plist;在其他系统上,openssl cms -verify -noverify -inform DER -in file.mobileprovision 效果相同。
没有 Mac,能在线查看描述文件吗?
能。本页在 Windows、Linux、macOS 上的现代浏览器里都能用,描述文件在你设备上的 Web Worker 里读取,所以描述文件、其中的 UDID 和证书都不会上传。
“A valid provisioning profile for this executable was not found”是什么意思?
设备上没有找到与这个包匹配的已安装描述文件。原因通常就是本页检查的几项之一:描述文件过期、设备 UDID 不在其中、Bundle ID 与它的 App ID 不匹配,或者签名证书不在它的开发者证书列表里。
我的 IPA 里用的是哪个描述文件?
就是 Payload/<App>.app/embedded.mobileprovision,页面在“描述文件”一节显示它的名称和 UUID。Xcode 导出时选中的描述文件可能和你以为的不一样,自动签名时尤其如此;看 UUID 就知道到底是哪一个。
描述文件里明明有这个 entitlement,还是装不上。
看“签入的值”那一列:不光键要有,值也必须被允许。用发布描述文件签名的包里 aps-environment 却是 development,是最常见的情况;钥匙串分组的团队前缀写错也很常见。再看签名证书那一行:证书续期后重新生成的描述文件里,已经没有旧证书了。
怎么查设备的 UDID?
连上设备,在访达里点设备名称下方那行文字,直到出现 UDID;或者在 Xcode 的 Window > Devices and Simulators 里查看。把它粘贴到设备列表下方的输入框里,就能和描述文件比对。
更新于
相关工具和教程
- 原生二进制概览:Mach-O 可执行文件完整的 load command、段、符号和依赖库
- X.509 证书解析:证书的全部字段和扩展
- 用 REA + Ghidra 免费分析原生二进制
- .mobileprovision 里装了什么?描述文件的解码与核对:用
security cms、openssl、plutil解码描述文件,再和codesign -d --entitlements的结果对比