收到一个脱敏后的 Invalid,或者看到 Accepted 却仍然无法让用户打开 App?macOS App 公证失败时,不要先重签或重复上传同一个文件:先保存提交 ID 和日志,确认故障阶段,再修复最终分发产物;即使状态是 Accepted,也必须继续完成 staple、Gatekeeper 检查和独立环境安装验收。
这篇内容适合三类人:使用 Developer ID 在 Mac App Store 之外发布 App 的独立开发者;在远程 Mac 或 CI 中维护 notarytool、stapler 自动化的人;以及发布包含插件、辅助工具、DMG 或 PKG 的小型团队。
里程碑一:先冻结公证现场,而不是盲目重试
遇到失败时,先把本次 Build、Archive、Export、Sign、Package、Submit 的产物和日志保存下来。至少记录以下信息:
- 脱敏后的提交 ID;
notarytool submit的完整输出;- 实际提交文件的 SHA-256 摘要;
- 当前
xcode-select --print-path的结果; - 最终文件名称、路径和容器类型;
- 返回状态是认证失败、上传未完成、处理中、
Invalid,还是Accepted后的后续失败。
Apple 的命令行工作流会返回提交 ID,你可以用这个 ID 继续查询状态和下载 JSON 日志;不要只依赖终端最后一行的摘要。notarytool log 返回的记录会包含问题路径、错误消息、严重级别和整体状态。(developer.apple.com)
先区分故障位置:
| 现象 | 优先判断 | 下一步 |
|---|---|---|
| 无法认证、无法上传 | 凭据、网络、Xcode 路径或会话环境 | 保留输出,检查凭据来源和当前工具链 |
| 状态长时间为处理中 | 服务端处理、网络或产物规模 | 不重复提交,先查询状态;必要时查看系统状态 |
Invalid |
最终产物的签名、权限、嵌套代码或封装问题 | 下载日志,按首个有效错误修复 |
Accepted 但 stapler 失败 |
容器不可写、网络不可达或提交对象不匹配 | 确认文件未被替换,再检查 staple |
| 用户下载后被 Gatekeeper 拦截 | 发布文件不是已验收文件,或票据、隔离属性、签名不一致 | 在干净 Mac 上重新测试真实下载文件 |
Apple 文档说明,公证服务通常会快速返回结果,但具体耗时会随文件数量、压缩方式和上传状态变化。官方给出的典型说明是:多数软件在 5 分钟内完成,98% 的软件在 15 分钟内完成;同时,文档还建议每天的公证提交不要超过 75 次。这些数字是服务端工作流提示,不是你可以用来强制判断“超时即失败”的固定规则。(developer.apple.com)
里程碑二:以最终 ZIP、DMG 或 PKG 为准
macOS App 公证失败,最容易踩的坑是只检查 Xcode 工程里的原始 .app,却没有检查真正上传给 Apple 的 ZIP、DMG 或 PKG。
正确顺序应当是:
- 完成 Build;
- 生成 Archive;
- Export 出 Developer ID 分发版本;
- 对导出的最终 App 检查签名;
- 创建 ZIP、DMG 或 PKG;
- 检查外层容器;
- 只提交准备交付给用户的那个文件。
Developer ID 不等于 Apple Distribution。Mac App Store 提交和 Mac App Store 之外的直接分发,是两条不同路径;直接分发需要使用适合 Developer ID 的签名和公证流程。Apple 也明确说明,ZIP、UDIF 格式 DMG 和 flat PKG 都可以进入公证流程,但 ZIP 本身不能直接签名或直接 staple。(developer.apple.com)
先检查签名主体和 Hardened Runtime:
codesign -dv --verbose=4 "Example.app"
codesign --verify --deep --strict --verbose=2 "Example.app"
codesign -d --entitlements :- "Example.app"
重点不是看到“有签名”就结束,而是核对:
- 签名身份是否为预期的 Developer ID;
- 是否包含安全时间戳;
- Hardened Runtime 是否启用;
- 是否残留
com.apple.security.get-task-allow; - entitlements 是否为格式正确的 XML;
- App 内的 Framework、插件、Login Item、Helper Tool、命令行工具是否分别签名;
- 签名完成后是否又被脚本、安装器或打包工具修改。
Apple 明确指出,签名后再修改 Bundle,可能直接导致二进制签名无效;自定义流程还可能因为 get-task-allow、entitlements 编码或不适用的证书类型而无法通过公证。(developer.apple.com)
如果你使用 PKG,检查安装器实际会投放什么。安装器里的辅助工具、守护进程或其他可执行内容不能只依赖外层 PKG 的签名来掩盖内部代码问题。对于 DMG,则要确认创建镜像后没有再替换 App、图标、背景文件或快捷方式。
里程碑三:Invalid 后先读首个有效错误
notarytool 显示 Invalid 后的日志路径
不要把所有日志行都当成同等重要。先按 JSON 中的 path、severity 和 message 排序,优先处理第一个指向具体文件或目录的有效错误。
示例命令如下,文件名和提交 ID 必须使用你自己的脱敏记录:
xcrun notarytool info "<SUBMISSION_ID>" \
--keychain-profile "<PROFILE_NAME>"
xcrun notarytool log "<SUBMISSION_ID>" \
--keychain-profile "<PROFILE_NAME>" \
"notary-log.json"
如果 info 显示 Invalid,再打开 notary-log.json,不要重新生成一个新提交来“验证是否只是偶发失败”。新的提交会让你失去原始产物与原始日志之间的对应关系。
常见错误可以这样分层处理:
- 签名无效:查看日志指向的二进制路径,再用
codesign --verify检查该组件;不要直接对整个 App 使用递归签名覆盖问题。 - 缺少安全时间戳:确认签名命令或 Xcode 导出流程使用了适合分发的签名方式,然后重新签署受影响的组件。
- 存在
get-task-allow:检查导出后的 entitlements,而不是只看 Debug 配置;该权限通常意味着你提交了调试用途的产物。 - entitlements 格式错误:检查 XML、编码和 BOM;Apple 文档特别提醒命令行工具与 App 的 entitlements 文件需要使用正确格式。(developer.apple.com)
- 证书类型不匹配:确认不是把 Mac App Store 使用的 Apple Distribution 身份拿来做 Developer ID 直接分发。
- 嵌套组件遗漏:沿日志中的路径向上检查 Framework、插件、Helper 和 Login Item,确认每一层都来自同一套最终产物。
这里的关键是“按路径修复”,而不是“增加签名命令数量”。递归签名可能暂时改变输出,却会掩盖组件为什么没有在正确阶段签名。
里程碑四:由内到外重新签名和封装
嵌套代码的处理顺序决定了后面的检查是否有效。建议按以下时间线回退并重做:
- 清理旧的导出目录,不覆盖原始失败产物;
- 修复最内层可执行文件、插件或 Framework;
- 为这些组件重新签名;
- 再签署 App Bundle;
- 重新创建 PKG 或 DMG;
- 若 DMG 中包含 PKG,再签署外层 DMG;
- 重新计算最终文件摘要;
- 只提交最外层的最终交付文件。
Apple 的封装文档给出的原则是:如果 App 位于 PKG 中,PKG 又位于 DMG 中,应先签 App,再创建并签 PKG,最后创建并签 DMG;嵌套容器应从最低层向最高层处理。直接分发时,通常只提交最外层容器进行公证。(developer.apple.com)
三种常见格式的决策不要混用:
- ZIP:适合直接解压 App,但 ZIP 不能直接签名,也不能直接 staple;应对其中的 App 或其他可 staple 项目处理后,再重新创建 ZIP。
- DMG:适合带有拖拽安装界面的单 App 分发;签名和 staple 都作用于 DMG 文件本身。
- PKG:适合需要把多个组件放入指定位置,或需要执行安装脚本的场景;应同时检查 PKG 内部将要安装的内容。
如果使用第三方安装器,而且安装器会投放其他可执行内容,不能默认一次公证覆盖所有阶段。Apple 的自定义工作流文档说明,这类场景可能需要先公证安装器的 payload,再把已公证内容放进外层安装器并再次公证外层文件。(developer.apple.com)
条件分支:该修复、回退还是重建
- 若日志只指向一个内层组件,保留当前提交记录,修复该组件后重新导出。
- 若多个路径同时出现签名无效,回退到 Archive 或 Export 阶段,重新生成干净产物。
- 若外层容器在签名后被修改,删除外层容器并重新封装,不要继续使用旧 DMG 或 PKG。
- 若凭据疑似错误,先验证当前凭据是否仍可用,再决定是否轮换;删除旧凭据前保存名称、来源和恢复方式。
- 若远程 Mac 的 Xcode 路径与本地不同,先固定
DEVELOPER_DIR或xcode-select,再比较两边的签名和封装输出。
里程碑五:Accepted 之后仍有发布验收
Accepted 后是否还要运行 stapler
需要。Accepted 表示 Apple 的公证服务接受了提交,并生成了可供 Gatekeeper 查询的票据;它不代表你正在分发的文件已经完成离线验收。
对 .app、.dmg 或 .pkg,可以继续执行:
xcrun stapler staple "<FINAL_FILE>"
xcrun stapler validate "<FINAL_FILE>"
ZIP 不能直接 staple。你应当对 ZIP 内需要交付的 App 或容器完成 staple,再重新创建一个新的 ZIP,并对这个新 ZIP 做摘要和测试。Apple 说明,staple 的价值在于让 Gatekeeper 即使无法联网,也能从分发文件中找到票据。(developer.apple.com)
随后使用 Gatekeeper 相关检查:
spctl --assess --type execute --verbose=4 "Example.app"
spctl --assess --type install --verbose=4 "Example.pkg"
spctl 的结果受测试 Mac 的安全策略影响,因此它不是唯一验收依据。你还需要把真实下载文件放到一台没有开发缓存、没有旧版本 Keychain 状态的 Mac 上,执行以下验收:
- 从实际下载地址取得文件,而不是直接测试构建目录;
- 删除旧 App,避免误启动旧版本;
- 断开网络或限制网络后测试安装;
- 从 DMG、PKG 或 ZIP 的真实流程开始;
- 检查首次启动时的 Gatekeeper 行为;
- 验证 App、插件、辅助工具和更新流程。
Apple 的发布文档也建议在不同于开发机的 Mac 上测试分发产物,因为开发机上的缓存、信任记录和本地工具链可能掩盖真实用户问题。(developer.apple.com)
里程碑六:远程 Mac 自动公证的固化
本地公证成功但远程 Mac 失败的定位方式
这种情况通常不是“远程 Mac 不能公证”,而是两台机器没有使用同一套输入条件。先逐项固定:
- Xcode 路径:记录
xcode-select --print-path,必要时使用DEVELOPER_DIR; - 工具版本:记录
xcrun notarytool --help的实际来源和脚本执行路径; - 凭据来源:优先使用 Keychain Profile 或受保护的密钥注入方式,避免把密码写入脚本;
- 产物摘要:提交前后都保存 SHA-256;
- 日志关联:把提交 ID、文件名、提交时间和构建编号写入同一份记录;
- 图形会话与 SSH:不要假设通过 SSH 执行的任务拥有和图形会话相同的 Keychain 解锁、环境变量和权限。
Apple 的迁移文档说明,安装了多个 Xcode 时,xcrun 使用哪个 notarytool 取决于当前选择的开发者目录;也可以通过 DEVELOPER_DIR 在单次命令中指定工具链。(developer.apple.com)
自动化任务还要明确四个边界:
- 上传阶段多久没有进展时记录超时;
Processing状态如何查询,而不是立即重传;- 哪些错误允许自动重试,哪些错误必须人工停止;
- 断线、重启或会话退出后,如何根据提交 ID 恢复,而不是重新提交。
远程 Mac 适合承载长期运行的签名、公证和发布链路,但前提是权限隔离、私钥保护和日志留存都已经设计好。你可以先阅读 远程 Mac 无人值守代码签名排障 相关内容,再把公证步骤接入现有发布脚本;如果还要处理 DMG 和 PKG,建议同时整理一份 macOS App 发布环境备份清单。
如果当前开发电脑无法稳定保留 Developer ID 私钥、固定 Xcode 工具链和长期公证日志,比较稳妥的做法是:先在本地完成一次故障定位,再把签名、公证、staple 和 Gatekeeper 验收迁移到权限隔离的常驻远程 Mac,并用一个非正式版本完整跑通。需要临时建立这类环境时,可以先查看 VMSPIN 的 Mac 远程方案,重点比较会话保持、SSH 访问、图形界面和任务恢复是否符合你的发布流程。
macOS App 公证失败真正消耗的,通常不是一次上传本身,而是错误产物、未保存日志、多版本 Xcode、SSH 会话差异和用户侧验收缺失叠加后的返工。与其在一台经常关机、磁盘紧张或无法稳定保管私钥的开发电脑上反复尝试,不如把发布链路拆成“固定工具链、保存提交 ID、修最终文件、独立 Mac 验收”四个阶段。对需要临时算力或测试环境的独立开发者来说,租用 VMSPIN 的远程 Mac 会比临时拼接本地 Mac、Windows 虚拟机或不稳定 CI 更容易保留完整发布现场;但如果你需要长期满负载编译、物理 USB 设备或完全自主管理硬件,购买并维护自己的 Mac 仍然更合适。