fastlane cert 深度解析:iOS 代码签名证书的自动创建、查找与吊销
本文围绕 fastlane 中 get_certificates(别名 cert)action 的官方文档展开,讲清这个 action 的完整行为流程、全部命令行参数与子命令、与 sigh 的联动机制,以及证书在本机 Keychain 中的验证原理。读完后,你可以独立完成证书自动化配置,并能从源码层面理解「何时新建证书、何时复用现有证书、何时抛出异常」的判定逻辑。
一、cert 是什么:定位与适用边界
cert 是 fastlane 工具链中负责 iOS 代码签名证书(code signing certificate) 管理的工具,它属于 fastlane 的 code_signing 分类(见 get_certificates.rb 中 self.category 返回 :code_signing),且仅在 :ios 平台生效(is_supported? 判断 platform == :ios)。
官方文档中有一个必须重视的提示:推荐使用 match 来生成和维护证书,cert 只在你需要完全掌控每一步、并且熟悉代码签名细节时才建议直接使用。这是因为 cert 的操作粒度更细,它把「查找、创建、导入 Keychain」的每个环节都暴露给了使用者,适合需要自定义证书策略的 CI 场景。
二、基本用法与行为语义
最简命令是:
fastlane cert
执行后它会检查本地机器上是否已安装可用的签名证书。只有当确实需要创建新证书时,cert 才会依次执行以下四步(见 runner.rb 的 create_certificate 方法):
- 创建一个新的私钥(private key);
- 创建一个新的证书签名请求(CSR,certificate signing request);
- 生成、下载并安装证书;
- 把生成的所有文件导入本地 Keychain。
三条关键行为语义:
-
cert 永远不会吊销你现有的证书。 当你无法再创建更多证书时,
cert会抛出异常——此时你必须手动吊销一个已有证书来腾出名额。这一点在源码中得到印证:create_certificate捕获创建失败后,若错误信息包含"You already have a current",会明确报出「已达到该类型证书数量上限」(见 runner.rb);若包含"You are not allowed to perform this operation."且创建的是 Distribution 证书,则提示「只有 Team Admin 才能创建 Distribution 证书」。 -
Apple ID 可以通过
-u传入:fastlane cert -u cert@example.com -
可以用
fastlane action cert查看全部可用命令与环境变量。 该命令由 fastlane 的命令文档系统生成,其参数列表直接来自Cert::Options.available_options(见 get_certificates.rb 中available_options的实现)。
还有一条文档中的硬性限制:cert 无法从 Apple Developer Portal 下载已有证书加私钥——因为私钥永远不会离开你的电脑。所以 cert 的复用逻辑是「本地已安装 / 本地有私钥缓存」两种情况(下文详述),而不是去门户下载私钥。
三、运行流程源码解析:launch → run → find / create
cert 的完整执行链是:action 层调用 Cert::Runner.new.launch(见 get_certificates.rb),Runner 内部流程如下(见 runner.rb):
launch 阶段:先执行 run 主流程,然后在 macOS 上用 FastlaneCore::CertChecker.installed? 验证新证书是否已正确装入 Keychain;若非 macOS(例如 Linux CI),则跳过验证并打印提示。
run 阶段的决策逻辑:
- 确保输出目录存在(
FileUtils.mkdir_p(Cert.config[:output_path])),并打印本次运行的配置摘要表; login登录 App Store Connect:若提供了api_key/api_key_path(App Store Connect API Key),走 token 认证,用户名变为可选;否则回退到 Apple ID 登录,此时会强制要求提供username(Cert.config.fetch(:username, force_ask: true));- 判定是否需要新建证书:若
force: true直接新建;否则调用find_existing_cert查找,只有查不到才新建; create_certificate成功即返回,失败则报UI.user_error!。
find_existing_cert 的三级判定(见 runner.rb)是理解「cert 何时不新建证书」的关键:
| 顺序 | 条件 | 行为 |
|---|---|---|
| 1 | 证书内容存在,且 CertChecker.installed? 判定该证书已装入本机 Keychain |
记录环境变量,直接复用,提示 "Found the certificate … which is installed on the local machine" |
| 2 | 本机未安装,但输出目录里存在同名 {certificate.id}.p12 私钥缓存 |
通过 FastlaneCore::KeychainImporter 把私钥和证书重新导入 Keychain 后复用,提示 "Found the cached certificate …" |
| 3 | 两者都不满足 | 记录错误、删除无用的裸证书文件,最终提示 "Couldn't find an existing certificate… creating a new one",进入新建流程 |
这个设计解释了文档那句话:cert 先检查本地是否有可用签名证书,只有必要时才走「建私钥 → 建 CSR → 下载安装 → 导入 Keychain」的完整链路。
四、全部参数与环境变量(Options 全表)
运行 fastlane action cert 可列出所有参数,其定义集中在 cert/lib/cert/options.rb。以下按源码逐项整理:
| 参数 | 短选项 | 环境变量 | 类型 / 默认值 | 说明 |
|---|---|---|---|---|
development |
- | CERT_DEVELOPMENT |
Boolean,默认 false |
创建 Development 证书而非 Distribution 证书 |
type |
- | CERT_TYPE |
字符串,可选 | 指定具体证书类型,优先于 development。取值:mac_installer_distribution、developer_id_installer、developer_id_application、developer_id_kext |
force |
- | CERT_FORCE |
Boolean,默认 false |
即使已有证书也强制新建 |
generate_apple_certs |
- | CERT_GENERATE_APPLE_CERTS |
Boolean,默认「macOS 且 Xcode ≥ 11」时自动为 true |
创建 Xcode 11 及以后使用的 Apple Development / Apple Distribution 证书类型 |
api_key_path |
- | CERT_API_KEY_PATH(兼容 DELIVER_API_KEY_PATH、APP_STORE_CONNECT_API_KEY_PATH) |
路径,可选 | App Store Connect API Key 的 JSON 文件路径,与 api_key 互斥 |
api_key |
- | CERT_API_KEY(兼容 DELIVER_API_KEY、APP_STORE_CONNECT_API_KEY) |
Hash,敏感字段 | 以 hash 形式传入 API Key,与 api_key_path 互斥 |
username |
-u |
CERT_USERNAME |
字符串,可选 | Apple ID。默认值会动态取 Appfile 中的 apple_dev_portal_id 或 apple_id |
team_id |
-b |
CERT_TEAM_ID |
字符串,可选 | 属于多个团队时指定团队 ID,默认取自 Appfile 的 team_id |
team_name |
-l |
CERT_TEAM_NAME |
字符串,可选 | 同上,按团队名指定,默认取自 Appfile 的 team_name |
filename |
-q |
CERT_FILE_NAME |
字符串,可选 | 证书落盘文件名的自定义;不带扩展名时会自动补 .cer |
output_path |
-o |
CERT_OUTPUT_PATH |
路径,默认 . |
证书与私钥统一存放的目录 |
keychain_path |
-k |
CERT_KEYCHAIN_PATH |
路径,可选 | 自定义 Keychain 路径。macOS 上默认为 ~/Library/Keychains/login.keychain(-db);非 macOS 平台传入会直接报错(Keychain 不支持) |
keychain_password |
-p |
CERT_KEYCHAIN_PASSWORD |
敏感字段,可选 | 首次在新 Mac 上访问证书时可能需要的密码(login 默认 Keychain 即 macOS 账户密码);非 macOS 平台传入会报错 |
skip_set_partition_list |
-P |
CERT_SKIP_SET_PARTITION_LIST |
Boolean,默认 false |
跳过设置 partition list(有时很耗时)。通常应保留该步骤,否则 Xcode 可能反复弹窗要求允许证书用于签名 |
platform |
- | CERT_PLATFORM |
字符串,默认 ios |
证书平台:ios、macos、tvos |
几个值得注意的默认值行为(均可在 options.rb 中核对):
generate_apple_certs是动态默认值:只要你在 macOS 上用 Xcode 11+ 运行,cert 就会默认创建 Apple Distribution / Apple Development 类型。测试代码 runner_spec.rb 正好覆盖了这一分叉:Xcode 10 用Spaceship.certificate.production,Xcode 11 用apple_distribution。- 证书类型如何由参数推导,见 runner.rb 的
certificate_types:type参数最高优先级;否则看generate_apple_certs(Enterprise/in-house 团队不使用 Apple Distribution,会回落到 iOS Distribution;development: true时用 Development 类型);最后按platform选择 iOS / macOS 的 Distribution 或 Development 类型。 - 一个已知限制(源码中明确 raise):截至源码注释的时间点,App Store Connect API 不允许通过 API Key 访问
DEVELOPER_ID_INSTALLER类型,该类型只能走 Apple ID 登录路径。
五、CLI 子命令:create 与 revoke_expired
除了作为 action 在 Fastfile 中使用,cert 还有独立的 CLI(见 commands_generator.rb):
fastlane cert create # 默认子命令,等价于 fastlane cert
fastlane cert revoke_expired # 吊销已过期的证书
revoke_expired 对应 Cert::Runner#revoke_expired_certs!(见 runner.rb):它登录 App Store Connect 后,用 certificates.reject(&:valid?) 筛出过期证书逐个调用 delete!。实现上有两个健壮性细节:单张证书吊销失败不会中断,会 UI.error 后继续吊销其余证书;结束时汇总吊销数量。runner_spec.rb 中有两条测试专门验证「正确挑选过期证书」和「一张失败不影响其余吊销」。
这条子命令恰好补上了文档中「cert 从不吊销你现有证书」留下的运维缺口:它只吊销过期的,绝不主动吊销仍有效的证书。
六、共享值与 sigh 联动
在 Fastfile 中,cert 执行完成后会向 lane_context 写入两个共享值(见 get_certificates.rb):
| 共享值 | 含义 |
|---|---|
CERT_FILE_PATH |
证书文件路径 |
CERT_CERTIFICATE_ID |
证书 ID |
同时它会把证书 ID 写入环境变量 ENV["SIGH_CERTIFICATE_ID"],注释写明「for further use in the sigh action」。而 sigh 的选项定义 中 SIGH_CERTIFICATE_ID 正是一个可用的环境变量名——这就是两条命令之间的隐式握手:cert 刚安装的证书 ID 会被紧随其后的 sigh 直接采用。
文档推荐的经典组合是把两者放进同一个 lane:
lane :beta do
cert
sigh(force: true)
end
其中 force: true 让 sigh 每次运行都重新生成描述文件(provisioning profile),从而保证始终使用当前本机已安装的、正确的签名证书。文档中的组合示例 cert && sigh 表达的就是同样的语义:先确保证书存在,再确保描述文件与证书匹配。
七、Keychain 验证与 WWDR 中间证书
「cert 检查本地证书是否安装」这一步由 fastlane_core/lib/fastlane_core/cert_checker.rb 实现,其中有几个对排障很重要的事实:
CertChecker.installed?(path, in_keychain:)的计算方式是:取证书文件的 SHA-1 指纹(sha1_fingerprint),与security find-identity -v -p codesigning列出的本机有效身份比对(见 cert_checker.rb)。- 若机器上「0 valid identities found」,它会打印诊断建议:手动执行
security find-identity -v -p codesigning,并提示检查是否存在过期的 WWDR 中间证书这一经典问题。 - 更关键的是
install_missing_wwdr_certificates:每次列取身份前,cert 相关流程会检查 Keychain 中是否安装齐了 Apple 的 WWDR 中间证书(G2–G6 等,见 cert_checker.rb 中的WWDRCA_CERTIFICATES清单),缺哪个就从 Apple 证书权威站点下载哪个并security import进去。WWDR 中间证书过期是「证书明明在、签名却报信任链错误」的高频根因,fastlane 在这里做了自动修复。
八、密码如何存储
cert 登录 App Store Connect 使用的凭据由 fastlane 的 CredentialsManager 统一管理(见 credentials_manager/README.md):
- 默认情况下,Apple ID 密码存储在 macOS Keychain 中,只保存在你的本机,从不离开你的电脑;
- 也可以用环境变量
FASTLANE_USER/FASTLANE_PASSWORD传入凭据; - 设置
FASTLANE_DONT_STORE_PASSWORD为"1"可禁止密码写入 Keychain; - 也可以走 App Store Connect API Key(
api_key/api_key_path)完全绕过 Apple ID 密码——这在 CI 中是推荐做法。
九、实用 Tips
- 查看本机证书身份:
security find-identity -v -p codesigning,这是排查「证书装了但 fastlane/Xcode 找不到」问题的第一步。 - 多团队协作:在多个开发团队之间切换时,用
-b team_id或在 Appfile 中配置team_id,避免登录歧义。 - CI 中的 Keychain 参数:
keychain_path/keychain_password/skip_set_partition_list三个参数只在 macOS 上有效,在 Linux 容器里传入会直接校验失败,CI 配置中注意区分平台。 - 非 macOS 平台的限制:
launch会跳过 Keychain 验证与导入(打印 "Skipping importing certificates as it would not work on this operating system."),也就是说证书的 Keychain 安装步骤本质上依赖 macOS 环境,Linux 上只能完成「生成 + 下载」部分。 - 进阶查看描述文件:文档提到可以安装 ProvisionQL 这类 Finder 扩展来以 Quick Look 方式直观查看
mobileprovision文件内容,方便核对描述文件绑定的证书是否与cert产出的一致。
十、小结
cert 的行为可以归纳为一张决策表:有 API Key 或 Apple ID 可登录 → 列出该类型全部证书 → 已在本机 Keychain 中则复用 → 有本地 p12 缓存则重导入复用 → 否则新建(CSR + 下载 + Keychain 导入)→ macOS 上再做 SHA-1 指纹验证;force: true 则跳过查找直接新建。它从不主动吊销有效证书,数量上限到达时报错,而清理过期名额的活儿交给 fastlane cert revoke_expired。配合 lane_context 共享值与 SIGH_CERTIFICATE_ID 环境变量,cert 与 sigh 串联起来,就构成了 fastlane 中「证书 → 描述文件」的自动化基石。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00