Electron 应用自动化构建
前言
作为一位前端开发对 Electron 一定不会陌生,Electron 的应用目前来说越来越多,作为程序员最熟悉的应该就是 VSCode。
使用 Web 技术栈即可构建一个跨平台的桌面客户端可谓十分方便,如果你正在开发一款 Electron 应用,并打算将其打包发布,那你就应该为其添加代码签名。但是对于 Electron 上线发布来说,如何自动化的打包和签名 (Windows & macOS 需要签名) 来提高我们的生产力和减少人为产生的 Bug 是一个非常重要的问题
接下去我们结合一款最高 30w+ 日活的桌面产品聊聊如何做自动化的打包和签名
以下 Electron 的打包库我们使用 electron-builder (签名和公证等流程其他打包库同样适用)
macOS
macOS 的打包签名必须使用 macOS 系统完成,除了这一要求外,还需要一下两个额外条件
签名
对于 electron-builder 来说,macOS 的代码签名我们只需提供 CSC_LINK 和 CSC_KEY_PASSWORD 这两个信息即可(官方文档: https://www.electron.build/code-signing)
# 提供证书信息
export CSC_LINK="/path/to/certificate"
export CSC_KEY_PASSWORD="xxx"
# 开始打包 ...
yarn install
yarn build
CSC_LINK指向了签名证书的路径(可以是https或者file://或者本地路径)CSC_KEY_PASSWORD是上面提供的证书的密码
签名这个步骤的详细流程可以在 electron-builder 的开源代码中找到 packages/app-builder-lib/src/codeSign/macCodeSign.ts 里面找到
大致流程是通过提供的签名证书创建一个 keychain (即钥匙串), 然后通过 codesign 命令调用新创建的钥匙串进行代码签名
1. 提供证书变量
export CSC_LINK="/path/to/certificate"
export CSC_KEY_PASSWORD="xxx"
2. 创建新的 keychain
# keychain 路径 (通过一定逻辑生成的文件路径,可以认为是一个随机的临时路径)
keychain_file_path='~/xxx'
# keychain 密码 (同样是临时生成的随机密码)
keychain_password='xxx'
# 生成 keychain 前会判断 keychain_file_path 是否已存在 keychain
# 有的话就删除然后走下面的流程重新创建
security create-keychain -p $keychain_password $keychain_file_path
security unlock-keychain -p $keychain_password $keychain_file_path
security set-keychain-settings $keychain_file_path
security list-keychains -d user -s $keychain_file_path
3. 导入证书到 keychain 中
security import $CSC_LINK -k $keychain_file_path -T /usr/bin/codesign -T /usr/bin/productbuild -P $CSC_KEY_PASSWORD
security set-key-partition-list -S apple-tool:,apple: -s -k $CSC_KEY_PASSWORD $keychain_file_path
4. 查看是否有代码签名证书
查找签名证书并返回名称
security find-identity -v -p codesigning $keychain_file_path
也可以不指定 keychain_file_path 来查找
security find-identity -v -p codesigning
5. 签名
# name 是上一步查找证书返回的名称
codesign --deep --force --sign $name --keychain $keychain_file_path ElectronApp.app
公证
在 macOS 上面比较特殊,除了需要对应用签名外还需要进行公证,公证的详细流程可以参考: macOS 签名公证,这里我们就不过多赘述
公证这一步我们可以使用 Electron 官方提供的 electron/notarize 来进行自动化的公证
下面是官方提供的简单实用方法:
import { notarize } from '@electron/notarize';
async function packageTask () {
// Package your app here, and code sign with hardened runtime
await notarize({
appBundleId,
appPath,
appleId,
appleIdPassword,
ascProvider, // This parameter is optional
});
}
在 electron-builder 中,我们可以在 afterSign 这个 hook 里面完成这一步
const { notarize } = require('@electron/notarize')
const config = {
// ...,
afterSign: async (context) => {
const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== 'darwin') {
// 如果不是 macOS,跳过公证
return;
}
const appName = context.packager.appInfo.productFilename;
return await notarize({
appBundleId: yourAppBundleId,
appPath: `${appOutDir}/${appName}.app`,
// 我们将公证需要用到的信息放在 process.env 中
appleId: process.env.NOTARIZE_APPLE_ID,
appleIdPassword: process.env.NOTARIZE_APPLE_PASSWORD,
ascProvider: process.env.NOTARIZE_APPLE_TEAM_ID,
});
},
}
module.exports = config;
Windows
相对于 macOS 来说,Windows 的打包流程少了公证这步,但 Windows 的自动化流程可能是很多 Electron 应用实现打包自动化最大的拦路虎,这中间很大一部分障碍是: 我们无法直接拿到 Windows 代码签名的证书 (无法直接拿到 .pfx 或者 .cer 证书)
Windows 的代码签名证书简单说可以分为: EV 代码签名证书 和 非 EV 代码签名证书, EV 代码签名证书是安全性最高的,系统和杀毒软件会无条件信任经过 EV 代码签名证书签名后的应用。而没有经过 EV 代码签名证书签名过的应用打开会有安全警告。EV 代码签名证书的更多信息可以查看:https://www.globalsign.com/en/code-signing-certificate/ev-code-signing-certificates
以下 Windows 的签名打包流程我们只讨论 EV 代码签名证书的打包流程
EV 代码签名证书安全性最高,那么它自然会被特殊对待:我们无法直接拿到 EV 代码签名的证书文件 (类似 .pfx 结尾的证书文件)。
当我们去购买 EV 代码签名证书时,证书颁发机构 (CA) 会将证书放置在一个物理 USB 里面,并将这个物理 USB 邮寄给我们。然后我们还需要下载安装 CA 提供的驱动程序后才能使用这个签名证书
物理 USB 签名流程
当证书和驱动程序等环境都配置正确后,我们需要将证书信息提供给 electron-builder
const config = {
// ...
win: {
target: 'nsis',
// 官方文档:https://www.electron.build/configuration/win
certificateSubjectName: '你的证书名称',
},
};
module.exports = config;
可以通过在 PowerShell 上面执行以下命令拿到代码签名的证书信息
Get-ChildItem -Recurse Cert: -CodeSigningCert | Select-Object -Property Subject,PSParentPath,Thumbprint
当证书信息配置完成后直接执行打包命令,在需要代码签名时,会自动弹出一个窗口,在窗口中输入签名证书的密码后,electron-builder 就会自动对我们的程序进行代码命令
关于 Windows 的签名流程可以在 packages/app-builder-lib/src/codeSign/windowsCodeSign.ts 中找到,我们同样简单看下它做了什么
找到签名证书
electron-builder 会通过 PowerShell 执行一下命令拿到本机上面全部的打包证书并将输出转换为 json 格式
Get-ChildItem -Recurse Cert: -CodeSigningCert | Select-Object -Property Subject,PSParentPath,Thumbprint | ConvertTo-Json -Compress
在上面我们提供了 certificateSubjectName 这个信息,那么通过匹配全部的证书列表就能找到对应的代码签名证书的指纹(上面输出信息中的 Thumbprint)
代码签名
拿到证书指纹 (Thumbprint) 后就可以开始代码签名了
signtool.exe sign /v /fd sha256 /sha1 <thumbprint> /sm /as electron-app.exe
直接执行 signtool.exe 会报错,需要进入 signtool 的安装目录,默认安装位置在
C:\Program Files (x86)\Windows Kits\<SDK version>\bin\<version number>\<CPU architecture>\signtool.exe
完成后可以右键查看生成的 exe 文件属性的 Digital Signatures 来查看是否有签名成功

使用 HSM 版本签名证书实现自动化
上面因为证书存放在物理 USB 中,并且打包签名过程需要人工输入密码等原因,无法很好的实现自动化流程。而直接将证书导出为文件发送给我们是非常危险的行为。像这种问题很多云服务厂商提供了 HSM 帮我们解决这类问题(HSM 全称为 Hardware security module,意思为 硬件安全模块,可以很好的帮我们保护证书安全,当然 HSM 在云服务上面需要另收费)
但除了物理 USB 包装的证书外,主流的 CA 都会提供 HSM 版本的 EV 代码签名证书,以 GlobalSign 为例,提供了 USB 版本和 HSM 版本

购买和使用
关于 HSM 如何申请和使用,以 AWS 的 CloudHSM 为例,AWS 提供了非常完善的搭建和使用文档 https://docs.aws.amazon.com/zh_cn/cloudhsm/latest/userguide/introduction.html
购买和安装证书可以搭配 GlobalSign 文档食用:https://support.globalsign.com/code-signing/download-and-install-ev-code-signing-certificate
打包签名
在 HSM 的所有环境都搭建完成后,无须做任何调整即可直接开始打包(打包过程中也不再需要输入密码)
注意:需要提供 admin 权限给 PowerShell,否则可能会遇见类似这样的报错
SignTool Error: No certificates were found that met all the given criteria.
The following certificates were considered:
Issued to: xxxxx
Issued by: GlobalSign GCC R45 EV CodeSigning CA 2020
Expires: Fri Dec 01 06:58:19 2023
SHA1 hash: xxxx
After EKU filter, 1 certs were left.
After expiry filter, 1 certs were left.
After Hash filter, 1 certs were left.
After Private Key filter, 0 certs were left.
Linux
Linux 是最简单的,无须签名等流程