Provisioning Profile 过期导致 build 被拒:6 个原因 + 完整修复流

Build 上传后 Apple 发邮件说"provisioning profile expired"——证书 / profile 都有有效期。

你在 Xcode 里 Archive(或推 CI),通过 Organizer 或 Transporter 上传,几分钟内收到 Apple 自动邮件,标题 “App Store Connect: Issues with your app”,正文写 “the provisioning profile included in the bundle has expired”“the signing certificate used to sign your app is no longer valid”。build 根本没出现在 TestFlight,处理阶段就被拒了,审核员都没看到。

iOS 签名有两个有效期独立的产物:distribution 证书(建立后 1 年),和 provisioning profile(建立后 1 年,绑定的 cert 过期也作废)。CI/CD 经常用几个月前 check 进 config 仓库的旧 profile。修复是机械操作——重生过期那个产物再 rebuild——但先搞清是哪个过期能省一小时。

常见原因

按命中率排序。

1. Distribution certificate 过期

你的 iOS Distribution 证书 1 年到了。所有绑定它的 provisioning profile 同一刻全失效。你没做任何事;cert 静默过期。

如何判断Apple Developer → Certificates。找 “iOS Distribution” 行,看到期日是不是已经过去;状态列写 Expired

2. Provisioning profile 1 年到期

cert 还有效但 profile 是一年多前生成的。Profile 不自动续期,必须手动重生。

如何判断:Apple Developer → Profiles → 过滤 App Store / Ad Hoc。看 “Expires” 列,过了今天的就是死的。

3. CI/CD 缓存了几个月前的 profile

CI 把 profile 文件放在私有仓库或 secret manager 里没重拉。即使你在 Developer Portal 重生了新 profile,CI 还在用老的。

如何判断:SSH 进 CI runner(或查 secret manager)。打开 .mobileprovision

security cms -D -i embedded.mobileprovision | grep -A1 "ExpirationDate"

文件里的到期日比 Developer Portal 上的早,CI 就是过期的。

4. App ID 变了但 profile 没跟上

改了 bundle ID 或加了新 entitlement(Push、HealthKit)。原有 profile 的能力和 App ID 的 capabilities 不再匹配,Apple 拒签。

如何判断:Apple Developer → Identifiers → 你的 App ID → 看 Capabilities。再 Profiles → 点你的 distribution profile → 确认列出的 Capabilities 完全一致。漂移 = 拒回。

5. Apple Developer Program 会员到期

99 美元/年续费没成功(卡被拒、卡过期)。会员到期那一刻起所有 cert 和 profile 全失效。

如何判断:Apple Developer → Membership。续费日已过且状态不是 “Active”,整个账号都死了,得续费才能动。

6. WWDR 中间证书过期或本机没有

Apple 的 WWDR (Worldwide Developer Relations) 中间证书有自己的有效期。你本机 Keychain 或 CI runner 里可能没装最新版。Distribution cert 即使有效,证书链验不过。

如何判断:去 Apple WWDR 页。下载当前 G3 / G4 中间证书。导入 Keychain,看有效期;很多 CI 镜像里还是老版本。

动手前先确认

  • 完整记下拒回邮件原文;“expired”“invalid” 对应不同修法。
  • 确认你在哪个 team 下签名——多 team 账号能用错 cert 签名而不报警。
  • 改之前把当前 cert 和 profile 文件备份到本地(.p12 导出)。
  • 弄清问题是只在 CI 出现还是本机也复现;只在 CI 出就先查 secret manager。

需要收集的信息

  • 拒回邮件完整原文 + 错误码(如 ITMS-90161、ITMS-90201)。
  • 你的 iOS Distribution 证书名、指纹、到期日。
  • build 用的 provisioning profile 名、UUID、到期日。
  • CI 构建日志里 code signing 那几行。
  • Apple Developer Program 会员状态和续费日。

最短修复路径

Step 1:识别哪个产物过期

拒回邮件里搜关键词:

  • “signing certificate… expired” → cert 问题(Step 2)。
  • “provisioning profile… expired” → profile 问题(Step 3)。
  • “missing entitlements” → App ID 漂移(Step 5)。
  • “membership” → 会员到期(Step 6)。

文案模糊就 Step 2 和 Step 3 都跑一遍。

Step 2:重生 distribution 证书

Apple Developer → Certificates → ”+” → iOS Distribution → Create。

需要从 Keychain Access 生成 CertificateSigningRequest (CSR):

Keychain Access → Certificate Assistant → Request a Certificate From a Certificate Authority
Email: your@team.com
Common Name: Your Name
Saved to disk → CertificateSigningRequest.certSigningRequest

上传 CSR。下载新 .cer,双击装到 Keychain。把 cert + 私钥一起导出 .p12 备份 + 给 CI 用:

Keychain Access → My Certificates → 右键 → Export

Step 3:重生 provisioning profile

Apple Developer → Profiles → ”+” → App Store(或 Ad Hoc / In House,按分发方式)。

  • 选你的 App ID。
  • 选 Step 2 的新 distribution cert。
  • 选设备(仅 Ad Hoc)。
  • 命名带日期后缀:AppStore_Acme_2027-05
  • Generate,Download。

Xcode 里双击 .mobileprovision 安装。或放到 ~/Library/MobileDevice/Provisioning Profiles/

Step 4:更新 CI/CD secrets

CI 按文件引用 profile:

# 在 secret manager / git repo 里替换文件
# 例如 GitHub Actions
gh secret set IOS_PROVISIONING_PROFILE_BASE64 --body "$(base64 -i AppStore_Acme_2027-05.mobileprovision)"
gh secret set IOS_CERT_P12_BASE64 --body "$(base64 -i DistributionCert.p12)"
gh secret set IOS_CERT_P12_PASSWORD --body "your-cert-password"

CI workflow 里每次跑都解码并导入:

- name: Install signing assets
  run: |
    echo "${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }}" | base64 -d > profile.mobileprovision
    mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
    cp profile.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/

    echo "${{ secrets.IOS_CERT_P12_BASE64 }}" | base64 -d > cert.p12
    security create-keychain -p "" build.keychain
    security import cert.p12 -k build.keychain -P "${{ secrets.IOS_CERT_P12_PASSWORD }}" -T /usr/bin/codesign
    security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" build.keychain

Step 5:重 Archive 并上传

Xcode:

  • Product → Clean Build Folder。
  • Product → Archive(Generic iOS Device)。
  • Distribute App → App Store Connect → Upload。
  • 自动签名或手动选新 profile。

或 xcodebuild + xcrun altool / Transporter:

xcodebuild -workspace Acme.xcworkspace -scheme Acme -archivePath build/Acme.xcarchive archive
xcodebuild -exportArchive -archivePath build/Acme.xcarchive -exportPath build/ipa -exportOptionsPlist exportOptions.plist
xcrun altool --upload-app -f build/ipa/Acme.ipa -u "$ASC_USER" -p "@keychain:ASC_PASSWORD"

Step 6:长期改用 fastlane match

手动管理 cert 是反复过期痛的根源。fastlane match 把 cert 和 profile 加密存进私有 git 仓库(或 S3),任何开发者或 CI runner 都能拉到当前状态:

brew install fastlane
fastlane match init
fastlane match appstore

任何机器上一行 match appstore 拉到当前 cert + profile;match nuke distribution + 重生变成一行命令的年度仪式。

怎么确认已经修好

  • Xcode 里重 Archive 不报签名错误。
  • 上传到 App Store Connect 顺利过 validation 步。
  • 30-60 分钟内收到 processing 成功邮件。
  • 新 build 出现在 App Store Connect → TestFlight → Builds。
  • 2 小时内没收到后续 ITMS-90xxx 拒回邮件。

如果还是没修好

  1. 打开 Xcode → Settings → Accounts → Manage Certificates,确认看到新 cert 不是旧的。
  2. security find-identity -v -p codesigning 看本机能用哪些 cert;旧的删掉。
  3. 检查 Info.plist 里的 bundle ID 和 profile 绑的 App ID 是否完全匹配——大小写不一致都会挂。
  4. 把构建日志里的 profile UUID 和你下载的 UUID 对一遍;不一样说明 Xcode 缓存了老 profile。删掉 ~/Library/MobileDevice/Provisioning Profiles/ 里旧的,重启 Xcode。

预防建议

  • 任何 cert 或 profile 到期前 30 天加日历提醒;Apple 会发警告邮件但常进垃圾箱。
  • 第一天就用 fastlane match——免去手动 .p12 折腾,CI 轮换变得无痛。
  • 在团队共享文档里固定记录 profile 和 cert 到期日;月初续不是到期当天。
  • App Store 分发最多留 2 张 distribution cert(“当前”+“备用”),轮换避免依赖快过期的那张。
  • CI 加一步在每次构建打印 profile 到期日,过期 profile 当场报警。

相关阅读

标签: #排查