Apple Developer Technical Support 的官方排障示例只需要复制 1 个测试文件,再执行 1 条 codesign 命令,就能先把问题缩小到签名身份或执行环境。(Apple Developer Forums 代码签名排障资料)
判断框:适合先修复当前环境——图形终端可以签名、SSH 或 CI 失败,通常应先处理 Keychain 解锁和非交互授权;不适合继续清理证书——图形终端与 SSH 都失败,或身份确实缺少私钥、已过期、重复或无法建立信任链。
不要先撤销或重建证书。应使用同一用户、同一产物和同一签名身份,先对比图形会话与 SSH 结果,再依次检查数字身份、匹配私钥、Keychain 状态和证书信任链;只有资产确实损坏、重复或缺失时,才进入重新导入或轮换路径。
最后更新于 2026 年 8 月 25 日,排障流程核对自 Apple Developer Documentation、Apple Developer Forums 及 Xcode 构建设置参考。
这篇文章适合以下情况:
- 通过 SSH 登录远程 Mac 后签名失败,但图形界面中可以正常 Archive。
- 使用脚本、fastlane 或自托管 Runner 执行无人值守签名。
- 迁移打包环境后已经导入证书,却仍无法使用
Apple Distribution或Developer ID身份。
00第 1 步:先保留失败现场,不要马上动证书
codesign errSecInternalComponent 不是一个足够具体的根因,它可能出现在 Keychain 无法解锁、私钥无法使用、证书链不完整、用户上下文异常等多个环节。Apple 官方也明确提醒,这类问题经常发生在 SSH 和持续集成等非标准签名环境中。(Apple Developer Forums 代码签名排障资料)
先记录以下信息,建议保存到脱敏后的构建日志中:
- 实际执行任务的 macOS 用户名。
- 是图形终端、SSH、计划任务还是 CI Runner 执行。
- 使用的 Keychain 路径,例如
~/Library/Keychains/login.keychain-db。 - 完整错误上下文,包括
codesign、xcodebuild或 Archive 阶段的先后关系。 - 签名身份名称,例如
Apple Distribution: <TEAM_NAME> (<TEAM_ID>)。 - 待签名产物路径、Bundle ID 和 Team ID。
用户名、路径、证书哈希、Team ID、Bundle ID、密码和产物名称都应替换为占位符。不要在日志中保存 Keychain 密码,也不要为了“清干净”而直接删除全部身份、清空缓存或撤销证书,因为这些操作可能影响当前仍可用的发布链路。
Apple 的证书说明区分了“证书”和“数字身份”:证书只包含公钥及其证明信息,真正用于签名的私钥必须与证书匹配;两者组合后才构成可用的代码签名身份。(Apple 关于代码签名证书的技术说明 TN3161)
01第 2 步:用同一用户建立图形会话与 SSH 基线
先在图形终端中确认最小签名是否成功。不要直接拿完整 App Archive 做第一次测试,可以复制系统自带的测试二进制:
TEST_DIR="/tmp/codesign-diagnosis-<ID>"
mkdir -p "$TEST_DIR"
cp "/usr/bin/true" "$TEST_DIR/MyTrue"
security find-identity -p codesigning
codesign --force \
--sign "Apple Distribution: <TEAM_NAME> (<TEAM_ID>)" \
"$TEST_DIR/MyTrue"
如果实际使用的是 Developer ID Application,应把签名名称替换为真实身份。security find-identity -p codesigning 用于列出代码签名身份;身份如果根本不在匹配列表中,后续 codesign 就没有可用对象。相关行为和身份判定方式可参阅 Apple 的代码签名证书技术说明。(Apple 关于代码签名证书的技术说明 TN3161)
随后保持同一个 macOS 用户,退出图形会话,再通过 SSH 执行完全相同的命令。两次测试必须保持:
- 同一份
MyTrue文件。 - 同一个签名身份名称。
- 同一个用户和 Home 目录。
- 同一个 Keychain 搜索范围。
- 同一组
codesign参数。
为什么图形终端可以签名,SSH 会话却失败?
最常见的差异不是代码,而是会话状态。图形登录通常会自动解锁登录 Keychain,并允许用户在弹窗中批准私钥访问;退出图形会话后,SSH 登录不会自动完成这两件事。此时 codesign 需要交互授权,却没有可显示的图形界面,最终可能只返回 errSecInternalComponent。
如果图形终端和 SSH 都失败,则不要继续围绕 SSH 猜测,应直接进入签名身份、私钥和证书信任链检查。
02第 3 步:确认看到的是完整身份,而不是孤立证书
在图形终端和 SSH 会话中分别执行:
security find-identity -v -p codesigning
重点观察两个结果:
- 身份出现在 Matching identities,但没有出现在 Valid identities:优先检查证书是否过期、信任链是否缺少中间证书,或信任设置是否异常。
- 证书能在 Keychain Access 中看到,但身份列表不完整:优先怀疑匹配私钥不存在、私钥位于另一个 Keychain,或当前用户无法访问私钥。
- 出现多个同名身份:先记录 SHA-1 哈希和所在 Keychain,不要立即删除;确认哪个身份正在被构建脚本使用后,再制定回退方案。
Apple 的 TN3161 说明,codesign 会检查证书是否支持代码签名、证书是否处于有效期内,以及能否建立到受信任根证书的信任链。该文还特别指出,security find-identity 只搜索 Keychain 文件,并不覆盖所有数据保护 Keychain 场景。(Apple 关于代码签名证书的技术说明 TN3161)
怎样判断证书和私钥已经组成可用身份?
在 Keychain Access 中打开“我的证书”,展开目标 Apple Distribution 或 Developer ID Application 条目;正常情况下,证书下方应能看到对应的私钥。命令行日志中不要只依据证书名称判断,因为同名证书可能来自不同机器,真正决定能否签名的是证书与私钥是否组成同一个数字身份。
Apple 的证书概览说明,Apple Distribution 用于向 App Store Connect 提交或分发 iOS、iPadOS、macOS 等平台应用,而 Developer ID Application 用于在 Mac App Store 之外分发 macOS 应用,两者用途不能互相替代。(Apple Developer 证书类型概览)
可以把判断结果按下面方式记录:
- ✅ 证书存在,私钥存在,名称和 Team ID 匹配。
- ✅ 证书处于有效期,Keychain Access 显示证书有效。
- ⚠️ 证书存在但没有私钥:重新导入包含私钥的数字身份,而不是只导入
.cer。 - ⚠️ 证书和私钥分属不同 Keychain:优先把两者放入构建用户可访问的同一 Keychain。
- ❌ 证书过期、被撤销或信任链无法建立:确认影响范围后,再补齐中间证书或轮换资产。
03第 4 步:恢复 SSH 与 CI 的非交互式 Keychain 访问
如果图形终端成功、SSH 失败,先解锁实际存放私钥的 Keychain。Apple 官方示例使用 security unlock-keychain,并强调示例默认身份在登录 Keychain 中;如果使用其他 Keychain,必须指定实际路径。
示例命令如下,密码通过受保护的 CI Secret 注入,不要直接写入脚本:
BUILD_KEYCHAIN="$HOME/Library/Keychains/login.keychain-db"
security unlock-keychain \
-p "$KEYCHAIN_PASSWORD" \
"$BUILD_KEYCHAIN"
security find-identity \
-v \
-p codesigning \
"$BUILD_KEYCHAIN"
codesign --force \
--keychain "$BUILD_KEYCHAIN" \
--sign "Apple Distribution: <TEAM_NAME> (<TEAM_ID>)" \
"<PATH_TO_TEST_ARTIFACT>"
这里有几个容易被忽略的边界:
security unlock-keychain必须由实际构建用户执行。使用sudo后,Unix 用户可能变化,但安全上下文并没有按预期建立。- 不要把
sudo codesign当成常规修复方案。Root、sudo和混合执行上下文反而可能制造更多 Keychain 问题。 - 不要永久关闭安全机制,也不要把 Keychain 密码硬编码到仓库、Shell 历史或公开日志。
- 私钥访问权限应尽量只授予实际需要的签名工具。图形会话中出现私钥访问弹窗时,可在确认调用方和产物来源后选择“始终允许”;如果无法使用图形界面,应先在隔离环境中验证自动化授权策略,再应用到正式构建机。
Apple 的 Keychain 文档说明,macOS 支持多个 Keychain,应用通常依赖当前用户管理的默认 Keychain;因此“证书已经导入”并不等于“当前构建用户可以访问正确的私钥”。(Apple Keychain 技术文档)
04第 5 步:区分证书链问题与 Provisioning Profile 问题
当错误中出现类似下面的提示时,排查方向应转向信任链:
unable to build chain to self-signed root for signer "<SIGNING_IDENTITY>"
errSecInternalComponent
这与 Keychain 锁定并不完全相同。检查证书在 Keychain Access 中是否显示有效,确认相关 Apple 中间证书是否存在于正确的系统信任范围。不要仅因为 security find-identity 能看到证书,就认定它已经可以签名;官方排障资料明确展示了“能列出身份,但 Valid identities 为空”的情况。
随后检查 Provisioning Profile、Bundle ID、Team ID、entitlements 和签名身份是否互相匹配。Apple 的 App Store Profile 文档说明,上传配置文件需要匹配明确的 App ID,并且一个 App Store Profile 包含一个 distribution certificate。(Apple App Store Provisioning Profile 创建说明)
Xcode 侧至少核对这些构建设置:
xcodebuild \
-workspace "<WORKSPACE_PATH>" \
-scheme "<SCHEME_NAME>" \
-showBuildSettings | \
egrep "CODE_SIGN_IDENTITY|DEVELOPMENT_TEAM|PROVISIONING_PROFILE_SPECIFIER|CODE_SIGN_ENTITLEMENTS"
Apple 的 Build Settings Reference 将 CODE_SIGN_IDENTITY 定义为 Keychain 路径中有效代码签名证书的名称,并指出缺失或无效证书会导致构建错误;PROVISIONING_PROFILE_SPECIFIER 则必须对应有效的 Profile 名称或 UUID。(Apple Xcode 构建设置参考)
为什么身份列表里有证书,实际签名仍然会失败?
因为它只能回答“某个搜索范围内存在可识别的身份”,不能单独证明以下条件全部成立:
- 当前 SSH 用户能解锁存放私钥的 Keychain。
codesign被允许调用该私钥。- 证书链能建立到可信根。
- Profile 与 Bundle ID、Team ID 和 entitlements 匹配。
- Xcode 实际使用的签名身份与手工测试使用的是同一个身份。
如果只是 Profile 过期、能力发生变化或证书被撤销,应按 Apple 的 Profile 流程重新生成并下载;这与 errSecInternalComponent 本身不是同一层问题。(Apple Profile 编辑、下载与删除说明)
05第 6 步:用真实 Archive 完成无人值守验收
最小 codesign 成功,只能说明一个简单二进制完成签名,不能证明完整的 iOS / macOS 发布链路已经恢复。应使用脱敏测试项目继续执行真实 Archive、导出和上传前验证:
xcodebuild archive \
-workspace "<WORKSPACE_PATH>" \
-scheme "<SCHEME_NAME>" \
-configuration Release \
-archivePath "<ARCHIVE_PATH>" \
-allowProvisioningUpdates
如果团队使用手动签名,应显式指定与当前 Profile 匹配的身份和配置;如果使用自动签名,则确认构建用户具备所需的开发者账户访问条件。自动签名会根据 Bundle ID、能力、证书和 Profile 配置管理签名资产;手动签名则需要自行维护这些关系。
Archive 验收至少覆盖:
- 嵌套 Framework、Extension、Helper 和动态库是否全部完成签名。
- entitlements 是否与 Profile 授权一致。
Apple Distribution或Developer ID Application是否用于正确的分发目标。- 导出后的
.ipa或 macOS 产物是否能通过本地验证。 - 上传前是否仍出现 Keychain 弹窗、私钥授权错误或用户权限错误。
06第 7 步:用清单验收退出登录、重连和重启
完成修复后,不要只在当前 SSH 窗口中执行一次成功构建。至少安排一次完整的无人值守验收:
- [ ] 图形终端中最小
codesign成功,且没有未处理的授权弹窗。 - [ ] 退出图形会话后,SSH 登录仍能解锁正确的 Keychain。
- [ ] SSH 会话中
security find-identity -v -p codesigning显示预期身份。 - [ ] 任务失败后重试,不会因为残留锁定状态永久失败。
- [ ] 主机重启后,初始化脚本可以恢复用户、Keychain 和签名工具状态。
- [ ] 完整 Xcode Archive、导出和上传前验证均成功。
- [ ] 日志没有暴露 Keychain 密码、私钥内容、完整证书文件或账户令牌。
- [ ] 发生证书轮换时,旧身份仍有明确回退路径,且不会误删正在使用的 Profile。
如果只有退出图形会话后才失败,问题仍属于远程会话和 Keychain 生命周期管理;如果重启后无法恢复,则应检查构建用户的登录环境、Keychain 初始化步骤和 CI Runner 的安全上下文。某些 CI 系统只切换了传统 Unix 用户身份,却没有正确建立安全上下文。
当前方案如果是把签名任务挂在开发者个人 Mac 上,常见缺点是:机器关机或休眠会中断任务;Keychain 状态依赖人工图形登录;多个项目共享同一套证书和缓存,故障时难以判断影响范围。若使用临时构建环境,还可能遇到每次重连都要重新初始化用户和签名资产的问题。
完成命令行修复后,如果仍必须保持人工图形会话才能签名,建议评估一台具备完整权限、持续在线能力和稳定用户环境的远程 Mac,把 SSH、Archive 与 Keychain 初始化步骤固定下来。对于只需要临时发布、迁移打包环境或验证无人值守流程的团队,可先查看 NUKCLOUD 的远程 Mac 方案;如果需要按地区选择节点,也可以参考 NUKCLOUD 的美国远程 Mac 入口。核心不是把证书“重建一遍”,而是让同一个构建用户在每次真实签名时,都能安全、可重复地访问同一套签名资产。