【免费下载链接】App-Store-Connect-CLI
Fast, scriptable CLI for the App Store Connect API. Automate TestFlight, builds, submissions, signing, analytics, screenshots, subscriptions, and more
导读
本文围绕 App Store Connect CLI 仓库中的asc certificates export子命令设计文档展开,完整讲解如何把从 Apple Developer 网站下载的 X.509 证书与本地生成的私钥安全地打包为一个受密码保护的 PKCS#12 身份文件。你将掌握该命令的完整参数面、输入校验规则、三种输出渲染方式、跨平台(macOS / Linux / Windows)的权限硬化机制,以及它在证书生命周期(签发、下载、打包、续期)中的准确定位——这是一条不依赖公开 API、完全本地离线完成的证书装配链路。
背景:为什么需要一条本地证书装配命令
在 App Store Connect 的证书工作流里,CLI 现有的能力覆盖了两段:
asc certificates csr generate在本地生成 RSA 私钥与证书签名请求(CSR),产出 PEM 格式的--key-out与--csr-out两个文件;asc certificates create通过POST /v1/certificates提交 CSR,为公开 API 支持的证书类型签发证书。
但 Apple Push Services(推送证书)与 Website Push ID 证书无法通过 App Store Connect API 签发,只能到 Apple 的 Developer 网站上传 CSR 后手动下载 X.509 证书。下载完成后,CLI 缺少一种安全、确定性的方式把证书和本地私钥重新组合成一个可供工具链直接使用的身份文件。同时,公开 API 的CertificateType枚举与POST /v1/certificates并不暴露这两类证书类型,因此该命令绝不能伪装成可以远程签发或续期它们。
asc certificates export正是为了填补这一段空白而设计:它是一个纯粹的离线制品打包操作(offline artifact operation),不新增任何公开 API 端点,也不涉及 web-session 操作。它只负责把"证书 + 私钥 +(可选)CSR + 密码"四个输入,打包成一个标准的 PKCS#12 文件。
从源码结构看,该功能由 export.go 提供CertificatesExportCommand()实现,并在 certificates.go 的命令组注册表中挂载为certificates的子命令。
命令形态与参数说明
设计文档给出的完整命令形态如下:
asc certificates export \ --certificate ./push/push.cer \ --private-key ./push/push.key \ --password-file ./secrets/push.p12.password \ --p12-out ./push/push.p12 \ [--csr ./push/push.csr] [--force --confirm] \ [--output table|json|markdown] [--pretty (JSON only)]各参数的语义与约束如下:
| 参数 | 必填 | 说明 |
|---|---|---|
--certificate | 是 | Apple 签发的 X.509 证书路径,支持 DER(.cer)或 PEM 两种编码,且必须恰好包含一个证书对象 |
--private-key | 是 | 与证书匹配的私钥路径,要求是 PEM 格式、未加密的 RSA 或 EC 私钥(PRIVATE KEY/RSA PRIVATE KEY/EC PRIVATE KEY三种 PEM 块类型) |
--password-file | 是 | 存放 PKCS#12 密码的受保护文件路径,密码只能来自文件,不能作为命令行 flag 传入 |
--p12-out | 是 | 输出 PKCS#12 身份文件的路径 |
--csr | 否 | 可选 CSR(PEM 或 DER),提供时会对 PKCS#10 签名与公钥做额外校验 |
--force --confirm | 条件必填 | 当目标输出文件已存在时,必须同时给出两者才允许替换 |
--output | 否 | 渲染格式:table、json、markdown,默认table |
--pretty | 否 | 仅在与--output json组合时生效,用于美化 JSON 输出 |
关键的边界行为
--csr显式传空值会被判定为用法错误(usage error)而直接拒绝,而不是静默跳过该校验——见 export.go 中对csrSet与空值的处理:显式给出--csr ""会返回--csr must not be empty,只有完全不传该 flag 才会跳过 CSR 校验。--p12-out -会被拒绝(--p12-out must be a file path, not stdout),因为 PKCS#12 是二进制数据,绝不能写到 stdout。--confirm与--force必须成对出现:只给--confirm报--confirm requires --force,只给--force则报--confirm is required with --force。- 该命令不接受位置参数,任何多余参数都会返回用法错误。
- 用法类错误(缺失必填参数、非法 flag 组合、非法输出格式)返回退出码 2;输入校验失败或制品写入失败返回非零退出码。
- 成功的渲染器只把元数据写到 stdout,诊断信息写 stderr;证书、CSR、私钥、密码的字节永远不打印、不写日志。
命令的 LongHelp 中内置了三个可直接复制的示例,覆盖最小用法、JSON 输出用法和覆盖替换用法,见 export.go 的CertificatesExportCommand。
输入校验:先验证,后落盘
该命令在触碰目标路径之前,会对全部输入执行一轮完整的校验,任何一项失败都不会改变目标文件:
- 文件形态校验:所有输入必须是常规文件(regular file),采用仓库统一的"受保护文件策略"(protected-file policy)读取私钥与密码文件——打开时不跟随符号链接,并受 32 MiB(
maxCertificateExportFileSize = 32 << 20)的大小上限约束。 - 对象数量校验:证书、私钥、CSR 必须非空且恰好包含一个受支持的对象。多对象 PEM、非预期 PEM 块类型(如
CERTIFICATE REQUEST之外的块)或多余的 DER 尾部数据都会被拒绝。 - 证书有效性:证书必须当前有效(
NotBefore <= now < NotAfter),并且必须是叶子证书(IsCA为 false)。 - 密钥匹配:私钥派生出的公钥必须与证书公钥完全一致——实现通过
x509.MarshalPKIXPublicKey分别编码双方公钥后做字节比较(certificateExportPublicKeysEqual)。 - CSR 三方核对:若提供 CSR,则 CSR 的 PKCS#10 签名必须有效,且其公钥必须同时匹配证书公钥与私钥公钥。
- Windows 附加校验:受保护输入必须具有可验证的受限 DACL,具体见下文跨平台硬化一节。
密码文件的处理同样严格:只从--password-file读取,去掉末尾的一个 LF 或 CRLF 后,空结果直接拒绝(password file contains an empty password)。密码永远不会出现在 flag、诊断输出或遥测数据中。源码中trimCertificateExportPassword与clearCertificateExportBytes负责密码修剪与敏感内存清零,后者会在输入数据使用完毕后把缓冲区逐字节写 0,降低密钥材料残留在内存中的风险。
输出:单密钥单证书的 PKCS#12 与元数据渲染
输出的 PKCS#12 文件包含恰好一个私钥和一个叶子证书,使用仓库已有的现代 PKCS#12 编码器(software.sslmate.com/src/go-pkcs12的Modern2023变体,见 export.go 中modernpkcs12.Modern2023.WithRand(...).Encode(...)调用)。v1 版本不追加 CA 链。
文件以权限0600写入,并采用同目录原子写(atomic write)流程:先写临时 staging 文件,再替换/发布到目标名。以下情形一律拒绝:
- 目标是符号链接;
- 目标是目录或非常规文件;
- 输出路径与任一输入路径解析后相同(
--p12-out与输入冲突,含硬链接指向同一 inode 的情况); - 目标已存在但未同时提供
--force --confirm。
任何校验或编码失败都会让已存在的目标文件保持原样,不会被部分覆盖。
JSON 输出示例
设计文档给出的 JSON 结果只包含元数据,不含任何机密字节:
{ "operation": "certificates export", "certificatePath": "./push/push.cer", "privateKeyPath": "./push/push.key", "csrPath": "./push/push.csr", "p12Out": "./push/push.p12", "certificateSha256": "64-hex-fingerprint", "notBefore": "2026-08-30T00:00:00Z", "notAfter": "2027-08-30T00:00:00Z", "keyType": "RSA", "keySize": 2048, "privateKeyMatched": true, "csrMatched": true }对应的结果类型定义在 output_certificates.go 的CertificateExportResult中:csrPath与csrMatched带omitempty,未提供 CSR 时这两个字段会被省略。Table 与 Markdown 渲染器展示同一组非机密字段(字段名使用certificate_path、private_key_path、certificate_sha256、not_before、not_after、key_type、key_size、private_key_matched、csr_matched等下划线命名)。该结果类型已注册进输出渲染注册表,并有专门的单元测试验证 camelCase JSON 字段与 table/markdown 注册行为(见 output_certificates_test.go)。
值得注意的是,SHA-256 指纹取自证书的原始 DER 字节(sha256.Sum256(certificate.Raw)),可用于核对导出的证书与下载的.cer是否一致。
跨平台权限硬化:从 0600 到 DACL 与 ACL
这是本命令在安全设计上最重的部分,设计文档用大量篇幅说明,源码也按平台拆分实现。
Unix(Linux / macOS)侧
- 受保护输入(私钥、密码文件)必须满足
mode & 077 == 0,即权限至多为 0600,否则报permissions must be 0600 or more restrictive(见 export_security_unix.go)。 - 扩展 ACL 被视为独立元数据,与仓库
internal/rootfs的处理方式一致:0600 的 mode 位并不代表其他账户无法访问,因此带扩展 ACL 的输入会被拒绝,并附带移除提示——macOS 上为chmod -N,Linux 上为setfacl -b。Linux 通过system.posix_acl_access属性检测,macOS 通过acl_get_fd_np/acl_set_fd_np动态 libc 调用检测。 - 输出侧:staging 文件在写入任何 PKCS#12 字节之前,先剥离从目标目录继承来的扩展 ACL 并验证移除成功;随后校验文件的有效权限位。这是因为 FAT、exFAT 及部分 CIFS/FUSE 挂载会忽略或翻译请求的 0600 模式,如果文件系统不支持 0600,命令会 fail-closed 报错,而不是把可被他人读取的身份文件发布出去还报告成功。
- macOS 上普通文件创建可能在描述符被清理前就应用了可继承的 ACL,因此 staging 文件的创建使用 Darwin 的
open_extended原语,在创建时就显式附加一个空的、不可继承的 ACL(见 export_security_staging_darwin.go 中的secureopen.OpenNewFileNoFollowInRootNoInherit)。
Windows 侧
- 受保护输入通过有效 DACL(effective DACL)校验,而不是强制要求禁用继承:允许"继承的 allow 条目"存在,前提是每条生效条目都属于当前用户、SYSTEM 或 Administrators。这样
asc certificates csr generate写出的私钥以及常规方式创建的密码文件可以通过校验,而任何属于其他账户的条目都会被拒绝(见 export_security_windows.go 的certificateExportVerifyProtectedDACL)。 - 输出侧:先通过原生
NtCreateFile调用,在创建 staging 文件时就附加一个受保护的、仅当前用户可读/写/删除的 owner DACL(FILE_GENERIC_READ | FILE_GENERIC_WRITE | WRITE_DAC | DELETE访问掩码),并在写入任何 PKCS#12 字节前用同样的访问掩码回读验证。staging 文件在过渡期间不得暴露继承的访问权限。 - 对输入的校验允许多个受信账户条目存在;对输出则严格:DACL 必须受保护(
SE_DACL_PROTECTED)、不可继承、且仅包含当前用户单一条目。
路径解析硬化
- 输出路径在读取任何输入、创建任何目标目录之前就被检查。每个已存在的父组件都通过锚定的 no-follow 遍历检查;父组件是符号链接时,即使最终输出条目还不存在也会被拒绝(
refusing to write --p12-out through symlinked parent)。 - 目标父目录随后被固定(pin)为一个打开的目录句柄(
os.Root),再读取第一个输入字节。缺失的父组件通过该固定遍历创建,而不是基于路径的MkdirAll;staging、替换、发布全部经由这个持有句柄完成。这样一来,校验之后若某个父组件被换成符号链接,要么在固定遍历中失败,要么被直接忽略——身份文件发布进的是"被校验过的那个目录",而不是"路径稍后解析到的任何目录"。 - macOS 上仅针对这次根目录遍历,把标准的
/tmp、/var别名规范化到/private下的真实目标(见 export_destination_darwin.go),其他符号链接组件一律视为不可信并拒绝。 - 路径分类保持平台感知:尾部反斜杠只在
os.IsPathSeparator将其视为目录分隔符的平台上才当作目录分隔符;其余用户提供的路径字节全部原样保留。
发布阶段的 fail-closed 保证
身份文件发布采用 fail-closed 语义:必须使用原生 no-replace 重命名或原子硬链接发布。既不提供 rename 原语也不提供硬链接发布原语的文件系统直接返回错误,绝不通过"把字节拷进一个可见目标"的方式兜底,防止观察者看到写了一半的身份文件。如果硬链接发布成功但 staged 链接的删除失败,操作会返回错误而不是静默留下第二个包含机密的链接;此时目标文件保持完整,便于人工恢复。
兼容性、生命周期与边界
certificates export是纯增量命令,不影响现有证书、CSR、pass-type、merchant-ID、签名、认证或 web-session 行为。它的职责边界非常明确:
- 只打包制品:不创建、不续期、不吊销、不下载、不分类证书。
- 不碰系统状态:不访问钥匙串(keychain)、不注册 Website Push IDs、不启用 capabilities、不发送通知、不生成 token 凭据、不安排后台续期。
- 不做推测性分类:刻意不从证书 subject 字段推断服务用途,因为任意输入无法证明这种推断。
续期流程保持显式:通过 Apple Developer 网站获取新证书,然后重跑本命令;只有确实要替换已有身份文件时才使用--force。这与asc certificates create/asc certificates revoke/asc certificates update等 API 型子命令形成互补,共同构成完整的证书生命周期管理。
验证与测试策略
设计文档给出了完整的 RED-GREEN 验证思路,与仓库中的实现一一对应:
- 命令层测试:先从
certificates export未注册导致的失败测试开始;随后覆盖必填 flag、非法 flag 值、未知/位置参数、stdout/stderr 分离、table/JSON/Markdown 输出与退出码。仓库中的 certificates_export_command_test.go 就实现了 JSON 输出 + PKCS#12 解码回环测试:构造 RSA 密钥、证书与 CSR 后执行命令,断言 stderr 为空、stdout 不含BEGIN或密码明文,并把 JSON 解析后对p12结果做 PKCS#12 解码回环验证。 - 单元层测试:覆盖 DER/PEM 解析、RSA/EC 密钥格式、CSR 签名与密钥匹配、有效期窗口、受保护密码文件、符号链接与覆盖拒绝、原子替换、受限模式与 PKCS#12 解码回环——对应 export_test.go、export_security_test.go、export_security_windows_test.go 等测试文件。
- 手工验证:构建
/tmp/asc后,用合成证书、密钥与 CSR 分别执行合法与非法调用;只读的证书列表调用可以用来确认公开 API 尚未支持相关证书类型,但本功能不需要任何账户变更或 web session。
设计文档要求的仓库门禁命令:
make build make format make check-docs make lint ASC_BYPASS_KEYCHAIN=1 make test设计取舍:为什么不在 CLI 里自动化 Developer Portal
设计文档最后给出了两个重要的设计取舍:
- 远程步骤保持手动:把 Apple Developer 网站的自动化做进 CLI,会依赖一个不受支持、可变动的 web 契约,还会把证书签发和凭据操作纳入本命令的爆炸半径。保持远程步骤手动、只增加本地可测试的打包操作,恰好贴合公开 API 的边界。
- 不合并
csr-and-export:再增加一个certificates csr-and-export组合命令会与现有 CSR 生成器重复,并削弱续期流程的可组合性。独立的export命令让密钥/CSR 生成与下载证书的打包可以各自独立测试。
典型使用流程总结
一个完整的推送证书装配流程可以归结为:
asc certificates csr generate --key-out ./push/push.key --csr-out ./push/push.csr生成私钥与 CSR;- 到 Apple Developer 网站上传 CSR,下载
push.cer; - 用
printf '%s' 'your-password' > ./secrets/push.p12.password(或等效方式)准备受保护的密码文件; - 执行
asc certificates export --certificate ./push/push.cer --private-key ./push/push.key --password-file ./secrets/push.p12.password --p12-out ./push/push.p12; - 用
--csr传入原始 CSR 获得三重公钥核对,用--output json(可加--pretty)获得机器可读的元数据结果,用--force --confirm完成续期场景下的确定性替换。
整个过程零网络请求、零公开 API 依赖,输出的push.p12直接供签名、推送或内部分发工具链使用。
【免费下载链接】App-Store-Connect-CLI
Fast, scriptable CLI for the App Store Connect API. Automate TestFlight, builds, submissions, signing, analytics, screenshots, subscriptions, and more
相关推荐
AWS CLI 实战:使用 `aws acm export-certificate` 导出私有证书、证书链与加密私钥
AWS CLI 实战:使用 aws acm export certificate 导出私有证书、证书链与加密私钥 本指南围绕 AWS CLI 官方示例文档 ex
开发工具云原生运维App-Store-Connect-CLI 的 WinGet 打包与发布自动化指南:让 `winget install asc` 真正可用
App Store Connect CLI 的 WinGet 打包与发布自动化指南:让 winget install asc 真正可用 导读 本文基于 docs
Traefik 证书配置完全指南:用户自定义证书、证书存储与默认证书(Certificates & Stores)
Traefik 证书配置完全指南:用户自定义证书、证书存储与默认证书(Certificates & Stores) 本篇技术指南围绕 Traefik(云原生应用
后端API网关负载均衡微服务网络云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考