news 2026/9/6 22:36:35

Cypress 二进制代码签名实战:macOS 与 Windows 证书轮换及 electron-builder 签名链路解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Cypress 二进制代码签名实战:macOS 与 Windows 证书轮换及 electron-builder 签名链路解析

Cypress 二进制代码签名实战:macOS 与 Windows 证书轮换及 electron-builder 签名链路解析

【免费下载链接】cypressFast, easy and reliable testing for anything that runs in a browser.项目地址: https://gitcode.com/GitHub_Trending/cy/cypress

Cypress 在 CI 中构建 Windows 与 Mac 发行版时会执行代码签名,签名的具体工作由electron-buildercreate-build-artifacts构建任务中完成。本文基于仓库内的 code-signing 指南 展开,完整覆盖 Mac(Apple Developer ID 证书)与 Windows(SSL.com 证书)两条签名密钥的轮换流程,并结合 electron-builder.json、windows-sign.js 等仓库源码,深入讲解签名配置、公证(notarize)与远程签名委托的实际实现,读完后可理解 Cypress 发布产物"签名 → 公证 → 校验"的完整链路及其安全设计。

何时进行代码签名:CI 构建流程中的定位

code-signing 指南 开宗明义:Cypress 的Windows 和 Mac 发行版在 CI 构建时执行代码签名,签名动作由electron-buildercreate-build-artifacts任务中承担。指南同时声明了一个适用前提:读者应已熟悉 electron-builder 官方 Code Signing 文档中关于CSC_LINKCSC_KEY_PASSWORD等环境变量的一般约定——下文正是在这个前提下,把 Cypress 仓库中"证书如何申请、密钥如何存放、构建时如何消费"三件事讲透。

从源码结构看,这条签名链路的调用入口是 scripts/binary/build.ts 中的electronBuilder.build(...)调用:

await electronBuilder.build({ publish: 'never', config: { electronVersion, directories: { app: appFolder, output: outputFolder, }, icon: iconFilename, // for now we cannot pack source files in asar file // because electron-builder does not copy nested folders // from packages/*/node_modules asar: false, }, })

根目录 package.json 中声明的依赖为"electron-builder": "^25.1.8",因此本文所述的签名行为均针对该版本的 electron-builder API。构建流程中签名并非孤立步骤,而是嵌在如下顺序里:

  1. lerna run build/lerna run build-prod构建各 package(见 build.ts);
  2. 把各 package 的产物复制到 dist 目录;
  3. electronBuilder.build(...)打包并签名(Windows 走委托脚本,macOS 走CSC_LINK证书自动签名);
  4. 触发 electron-builder.json 中声明的afterPack/afterSign钩子完成后续加工与公证。

buildCypressApp的选项接口中还暴露了skipSigning?: boolean(见 build.ts),允许本地构建时跳过签名——签名失败时若设置了该选项会吞掉异常(build.ts),这为"本地打包调试"与"CI 正式发布"两种场景做了区分。

electron-builder.json:签名相关配置一览

仓库根目录的 electron-builder.json 是签名行为的配置中枢,关键项如下:

{ "productName": "Cypress", "appId": "com.electron.cypress", "mac": { "target": "zip", "forceCodeSigning": true, "hardenedRuntime": true, "entitlements": "./scripts/entitlements.mac.inherit.plist", "entitlementsInherit": "./scripts/entitlements.mac.inherit.plist", "type": "distribution" }, "win": { "signingHashAlgorithms": ["sha256"], "sign": "./scripts/windows-sign.js", "target": "dir" }, "afterPack": "./scripts/after-pack-hook.js", "afterSign": "./scripts/after-sign-hook.js" }

逐项说明:

  • mac.forceCodeSigning: true:强制要求提供签名证书(即上文CSC_LINK/CSC_KEY_PASSWORD),否则打包直接失败,保证 CI 产物不会静默产出未签名包。
  • mac.hardenedRuntime: true:启用 macOS 强化运行时,这是通过 Apple 公证的前提。
  • mac.entitlements/mac.entitlementsInherit:都指向 scripts/entitlements.mac.inherit.plist。该文件授予三项能力:
    • com.apple.security.cs.allow-jit—— 允许 JIT,Electron/V8 运行时所需;
    • com.apple.security.cs.allow-unsigned-executable-memory—— 允许未签名可执行内存;
    • com.apple.security.cs.allow-dyld-environment-variables—— 允许 dyld 环境变量,Cypress 二进制依赖环境变量注入(如云协议相关配置),从源码中大量的process.env消费可以推断其必要性。
  • win.signingHashAlgorithms: ["sha256"]:仅使用 SHA-256 摘要算法签名,符合当前 Windows Authenticode 要求。
  • win.sign: "./scripts/windows-sign.js":把 Windows 签名委托给自定义脚本(详见后文远程签名一节),而不是由 electron-builder 用本地证书直接签。
  • afterPack/afterSign:分别指向 after-pack-hook.js 与 after-sign-hook.js。前者负责把各 package 的node_modules拷入产物、翻转 Electron Fuses、生成 V8 快照等打包后加工(与签名无直接关系,但会改变最终进签/进公证的对象);后者负责 macOS 公证。

轮换 macOS 签名密钥:从 Apple 证书到 CircleCI 上下文

这部分完整继承 code-signing 指南 中 "Rotating the Mac code signing key" 的 4 步流程:

  1. 在 Mac 上用 Cypress 的 Apple 开发者计划身份登录 Xcode。
  2. 按 Apple 官方"Create, export, and delete signing certificates"文档操作:
    1. 先执行 "View signing certificates" 查看现有证书;
    2. 执行 "Create a signing certificate",提示选择类型时选择Developer ID Application(注意:用于应用分发签名的是 Developer ID Application 证书,而非 App Store 证书);
    3. 执行 "Export a signing certificate",导出时设置一个强口令,该口令之后会成为环境变量CSC_KEY_PASSWORD
  3. 把导出的、已加密的.p12文件上传到 Google Drive 的 Code Signing 文件夹,并生成一个公共的直接下载链接。
  4. 在 CircleCI 的test-runner:sign-mac-binary上下文中,把CSC_LINK设为该直接下载 URL,把CSC_KEY_PASSWORD设为加密 p12 时使用的口令。

几点值得强调的工程细节:

  • 凭据存放采用"外部密文文件 + 下载链接"模式:私钥从不落进代码仓库或 CI 明文环境变量,CSC_LINK只指向加密后的.p12CSC_KEY_PASSWORD单独存放。electron-builder 构建时会自动从CSC_LINK下载证书文件并用口令解密后签名。
  • 上下文按平台隔离:macOS 与 Windows 分别使用test-runner:sign-mac-binarytest-runner:sign-windows-binary两个独立上下文,密钥轮换互不影响,也便于按平台最小化授权。
  • 轮换时口令变更必须同步CSC_KEY_PASSWORD.p12的加密口令是绑定的,二者不同步会导致 CI 签名阶段解密失败。

源码纵深:签名之后还要"公证"(notarize)

macOS 上仅签名并不够,还需向 Apple 提交公证。Cypress 通过 after-sign-hook.js 在 electron-builder 的afterSign阶段完成,逻辑如下:

  1. 平台守卫process.platform !== 'darwin'时直接跳过(日志提示not Mac, skipping after sign hook);
  2. 逃生开关:设置了SKIP_NOTARIZATION环境变量时跳过公证,便于调试构建;
  3. 定位应用:在params.appOutDir下查找<productFilename>.app,找不到则抛错;appId硬编码为com.electron.cypress,与 electron-builder.json 中的appId一致;
  4. 三项必需凭据NOTARIZE_APP_APPLE_IDNOTARIZE_APP_PASSWORDNOTARIZE_APP_TEAM_ID,任一缺失立即抛错(注意公证用的是 Apple ID + App 专用密码 + Team ID,与签名用的 Developer ID 证书是两套凭据);
  5. 调用@electron/notarizenotarize(...)提交公证,失败会打印could not notarize application并向上抛出,使构建失败。

此外,build.ts 在 darwin 平台且未skipSigning时,会用 macOS 自带的 Gatekeeper 校验工具对产物做一次自证:

const args = ['-a', '-vvvv', appFolder] console.log(`cmd: spctl ${args.join(' ')}`) const sp = spawn('spctl', args, { stdio: 'inherit' })

即执行spctl -a -vvvv <产物路径>,退出码非 0 则构建失败——相当于在 CI 中模拟了"用户机器上 Gatekeeper 是否放行"的终检。

轮换 Windows 签名密钥:CSR、SSL.com 证书与 PFX 转换

这部分完整继承 code-signing 指南 中 "Rotating the Windows code signing key" 的 6 步流程。

第 1 步:用openssl生成私钥与 CSR

# generate a new private key openssl genrsa -out win-code-signing.key 4096 # create a CSR using the private key openssl req -new -key win-code-signing.key -out win-code-signing.csr

第 2 步:把 CSR 提交给 SSL.com(使用 Cypress 的 SSL.com 账户)换取证书

  • 若是续期(renewing),按 SSL.com 官方的 Renewing EV/OV and IV Certificates 指引操作;
  • 若是轮换(rotating),需联系 SSL.com 支持申请重新签发。

第 3 步:从 SSL.com 控制台获取完整证书链,以 ASCII-armored PEM 格式保存为win-code-signing.crt(内容形如-----BEGIN CERTIFICATE-----等块)。

第 4 步:用openssl把明文 PEM 的私钥与证书转为二进制的 PKCS#12/PFX 并加密,口令同样是强口令,之后成为CSC_KEY_PASSWORD

➜ openssl pkcs12 -export -inkey win-code-signing.key -in win-code-signing.crt -out encrypted-win-code-signing.pfx Enter Export Password: <password> Verifying - Enter Export Password: <password>

第 5 步:把encrypted-win-code-signing.pfx上传到 Google Drive 的 Code Signing 文件夹,获取公共直接下载链接。

第 6 步:在 CircleCI 的test-runner:sign-windows-binary上下文中,把CSC_LINK设为该直接下载 URL,把CSC_KEY_PASSWORD设为加密 pfx 的口令。

源码纵深:为什么 Windows 不直接用 pfx 本地签,而是远程签名

按 electron-builder 的一般约定,CSC_LINK指向的 pfx 会被拉取后在构建机上执行 Authenticode 签名。但 Cypress 的 windows-sign.js 文件头注释说明了偏离这一默认做法的原因:

This signing procedure only runs on windows binary builds to leverage remote signing in order to fullfil new requirements around OV and IV code signing.

即为了满足 SSL.com 自 2023 年 6 月起对 OV/IV 证书密钥存储的新要求(私钥不能长期离开受控环境),Cypress 改为把签名委托给 SSL.com 的远程签名服务。这与 electron-builder.json 中"sign": "./scripts/windows-sign.js"的委托配置相呼应,完整链路见 windows-sign.js 的sign(configuration)函数:

  1. 读取四个远程签名凭据(均来自 CI 环境变量,任一缺失则打印缺失项并process.exit(1)):

    • WINDOWS_SIGN_USER_NAMEWINDOWS_SIGN_USER_PASSWORD—— SSL.com 账户凭据;
    • WINDOWS_SIGN_CREDENTIAL_ID—— SSL.com 侧证书/凭据标识;
    • WINDOWS_SIGN_USER_TOTP—— TOTP 二次验证密钥。
  2. 下载并校验 SSL.com 的 CodeSignTool:脚本把工具版本钉死为 v1.3.2,并预置了下载包的 SHA-256 校验和:

    const CODE_SIGN_TOOL_VERSION = 'v1.3.2' const CODE_SIGN_TOOL_SHA256 = '4afc32e8b7f79bbe1de7e4e7049aaad4e0f754357613b9bbec0e3052f06fd36b'

    下载后先计算实际 SHA-256,与预期值不符立即抛错("Downloaded CodeSignTool archive checksum ... does not match expected ...")。注释还贴心地给出升级工具时的校验和再生成方法:curl -fSL <CODE_SIGN_TOOL_URL> | shasum -a 256。这保证了"能签我们二进制的工具"只能经受控的版本提升才会变化,防止供应链篡改。

  3. 调用 CodeSignTool 完成远程签名

    childProcess.execSync(`CodeSignTool.bat sign -input_file_path="${configuration.path}" -output_dir_path="${TEMP_DIR}" -credential_id="${CREDENTIAL_ID}" -username="${USER_NAME}" -password="${USER_PASSWORD}" -totp_secret="${USER_TOTP}"`)
  4. 回写产物:由于 CodeSignTool 无法在无交互确认的情况下原地覆盖文件,脚本先签出到临时目录os.tmpdir()/release/tmp,再把签名后的文件移回原位置覆盖(mv "${tempFile}" "${dir}"),对 electron-builder 而言签名对象路径不变。

从源码结构看,configuration.path由 electron-builder 在逐个签包文件时传入,因此该委托脚本只对 Windows 可执行文件生效,与electron-builder.jsonwin.target: "dir"(产出目录而非压缩包)相配合。

小结:两套密钥、两条链路、统一的 CI 凭据模型

维度macOSWindows
证书来源Apple Developer ID Application 证书SSL.com 签发的 OV/IV 证书
密钥形态加密.p12加密.pfx(PKCS#12)
凭据上下文test-runner:sign-mac-binarytest-runner:sign-windows-binary
核心环境变量CSC_LINKCSC_KEY_PASSWORDCSC_LINKCSC_KEY_PASSWORD,另加WINDOWS_SIGN_USER_NAME/_PASSWORD/_CREDENTIAL_ID/_USER_TOTP
签名方式electron-builder 本地签名(forceCodeSigning委托脚本走 SSL.com 远程签名(win.sign
签名后动作afterSign钩子公证(NOTARIZE_APP_*)+spctl自证

两个平台共享同一套"加密密钥文件存 Google Drive、CSC_LINK指下载链接、CSC_KEY_PASSWORD存口令"的凭据模型,差异在于 Windows 因密钥托管合规要求额外走了一条版本钉死、校验和校验的远程签名通道。对需要维护自签发布流程的 Electron 项目而言,这套"证书轮换 SOP + 委托签名 + 公证钩子 + Gatekeeper 自证"的组合是一个可以直接对照仓库文件复现参考的完整范本。

【免费下载链接】cypressFast, easy and reliable testing for anything that runs in a browser.项目地址: https://gitcode.com/GitHub_Trending/cy/cypress

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 22:35:11

基于NE555与CD4040的纯逻辑定时控制器设计:从分频计数到继电器驱动

简介&#xff1a;这是一份关于定时控制器逻辑电路设计的课程设计文档&#xff0c;面向电子、自动化等相关专业学生及数字电路爱好者&#xff0c;可帮助读者系统完成带数字电子钟的定时控制器设计任务。文档围绕数字钟单元、定时单元、控制器单元和继电器输出单元展开&#xff0…

作者头像 李华
网站建设 2026/9/6 22:32:14

理想速印机维修手册深度解析:从原理到故障排查的系统方法论

简介&#xff1a;速印机维修技术资料&#xff0c;覆盖理想SF9390C、5231C、5232ZL、5233C、5450、5350、5250、5050等多款一体化速印机型号&#xff0c;适用于办公设备维修工程师、速印机售后服务人员及院校实训教学。全文以PDF文档形式呈现&#xff0c;共1个文件&#xff0c;大…

作者头像 李华
网站建设 2026/9/6 22:29:04

果园履带运输机设计:从总体方案到田间实测的完整拆解

简介&#xff1a;《果园履带运输机设计说明书》是一份面向农机专业学生、果园机械研发人员及农业工程相关技术人员的完整设计文档&#xff0c;旨在解决传统果园机械通过性差、人工管理效率低的问题。该设计以矮砧密植型栽培模式为背景&#xff0c;采用履带式行走机构与剪叉式升…

作者头像 李华
网站建设 2026/9/6 22:28:56

真空常用计算公式与工程应用:抽速、漏率、流导全解析

简介&#xff1a;这份《真空常用计算公式页.pdf》是一份面向真空设备操作、系统设计与工艺维护人员的便携速查文档&#xff0c;集中整理了真空定义、真空度单位换算&#xff08;托与帕&#xff09;、平均自由程、流量、流导、压力等30余个核心概念&#xff0c;并给出玻义尔定律…

作者头像 李华