Skip to content

在 macOS 上使用 Electron Builder 集成 VSign 云签名 ​

适用于已开通 TrustAsia 云签服务,需要在 macOS 上通过 Electron Builder 为 Windows 应用程序和 NSIS 安装包自动签名的用户。整个流程为:准备云签信息 → 安装操作员证书 → 配置环境变量和签名脚本 → 构建并签名 → 检查结果。

本文以 Electron Builder 26.18.0 为配置示例,通过 ssigncode-mac-x86_64 执行 SHA256 单签名并附加时间戳。签名对象为 Windows 程序;macOS 原生应用的 Apple 签名和公证请另行配置。

1. 准备工具和签名信息 ​

开始前,请确认 Electron 项目可以正常构建,云签订单中的代码签名证书已签发,并准备好可联网的 Mac 和 macOS 版签名工具。工具安装包可联系技术支持获取。

设置 PIN、添加操作员、安装操作员证书和获取证书指纹的操作,沿用 在 macOS 上使用云签工具进行代码签名(云签)。若已在当前构建用户下完成安装,且现有证书和参数仍可正常使用,可直接复用。

设置 PIN 并获取安装 token ​

  1. 进入目标订单的 云签详情 → 签名安全设置,设置签名 PIN;已设置过的使用当前 PIN。
  2. 进入 云签详情 → 操作员管理,点击“添加”。
  3. 在新增操作员记录的 Token 列复制安装 token,供下一步安装操作员证书使用。

云签详情中的签名安全设置和 PIN 设置入口

在操作员管理中添加操作员并复制安装 token

信息获取位置与用途
签名 PIN在 云签详情 → 签名安全设置 中设置,对应安装和签名命令的 --key_pin。
操作员安装 token在 云签详情 → 操作员管理 中添加操作员后获取,对应安装命令的 --auth。
操作员证书指纹安装成功后,在 操作员管理 → 证书哈希 列获取,对应签名命令的 -u。
签名证书指纹在 订单详情 → 证书详情 → 指纹 中获取,对应签名命令的 --cert_hash。
签名服务器地址命令中的 -s 使用 https://vsign.trustasia.com。

准备命令行工具 ​

本文以 ssigncode-mac-x86_64 为例。请确认工具与执行机器的处理器架构、macOS 版本兼容,下文使用 <签名工具所在目录>、<待验证的已签名EXE文件的完整路径> 等占位示例。执行前请将尖括号及其中内容整体替换为实际值,并保留路径外侧的引号。

bash
chmod u+x "<签名工具所在目录>/ssigncode-mac-x86_64"

准备 Electron Builder ​

项目尚未安装 Electron Builder 时,可在项目根目录执行:

bash
npm install --save-dev electron-builder

本文的打包测试使用 Electron Builder 26.18.0。已有项目如支持下文的自定义签名配置,可沿用现有版本;配置项有差异时,请按对应版本文档调整。项目还需已安装自身使用的 Electron 版本。

2. 安装操作员证书并获取指纹 ​

在实际执行构建的 macOS 用户账号下,进入签名工具目录,按前文获取的信息执行安装命令:

bash
./ssigncode-mac-x86_64 install \
  --vsign \
  --auth '<操作员安装token>' \
  --key_pin '<签名PIN>' \
  -s https://vsign.trustasia.com

确认终端提示“获取操作员证书成功”和“安装操作员证书成功”。

安装操作员证书成功的终端输出

安装完成后,分别获取以下两个指纹:

  1. 返回 云签详情 → 操作员管理 并刷新,复制对应操作员的 证书哈希,作为操作员证书指纹。
  2. 进入目标代码签名证书的 订单详情 → 证书详情,复制 指纹 字段,作为签名证书指纹。

操作员管理中的证书哈希字段

证书详情中的指纹字段

核对目标代码签名证书的所属组织和有效期。操作员证书指纹、签名证书指纹和安装 token 分别用于不同参数,不能互相替代。

接入 Electron Builder 前,请先按 在 macOS 上使用云签工具进行代码签名(云签) 中的签名步骤,用同一工具、签名服务器、PIN 和两种证书指纹完成一个 EXE 文件的签名与验证。

3. 配置环境变量和签名脚本 ​

设置签名参数 ​

在项目根目录打开 macOS 默认的 zsh 终端,设置以下环境变量,替换工具路径和两种证书指纹:

bash
export VSIGN_TOOL="<签名工具所在目录>/ssigncode-mac-x86_64"
export VSIGN_OPERATOR_HASH="<操作员证书指纹>"
export VSIGN_CERT_HASH="<签名证书指纹>"

在同一终端中输入签名 PIN,输入内容不会显示在终端中:

bash
read -rs 'VSIGN_KEY_PIN?请输入签名 PIN:'; printf '\n'
export VSIGN_KEY_PIN
参数填写方法
VSIGN_TOOL命令行签名工具的绝对路径,路径含空格时也需完整填写。
VSIGN_OPERATOR_HASH操作员证书安装后,从 云签详情 → 操作员管理 → 证书哈希 获取的值。
VSIGN_CERT_HASH从目标代码签名证书的 订单详情 → 证书详情 → 指纹 获取的值。
VSIGN_KEY_PIN在 云签详情 → 签名安全设置 中设置的当前 PIN。

编写签名脚本 ​

在项目根目录新建 sign.cjs,写入以下内容。脚本沿用前文云签教程的 vsign 子命令、--cert_from csign、服务器地址和证书参数。

javascript
const fs = require('node:fs');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const { randomUUID } = require('node:crypto');

module.exports = async function sign(configuration) {
  const required = [
    'VSIGN_TOOL',
    'VSIGN_OPERATOR_HASH',
    'VSIGN_CERT_HASH',
    'VSIGN_KEY_PIN'
  ];
  for (const name of required) {
    if (!process.env[name]) throw new Error(`请设置环境变量 ${name}`);
  }
  // 本次演示采用 SHA256,检查构建配置是否与示例一致。
  if (configuration.hash !== 'sha256') {
    throw new Error('本示例配置为 SHA256 单签名,请检查 signingHashAlgorithms 配置');
  }

  const pin = process.env.VSIGN_KEY_PIN;
  const input = path.resolve(configuration.path);
  const extension = path.extname(input);
  const output = path.join(
    path.dirname(input),
    `${path.basename(input, extension)}.signing-${randomUUID()}${extension}`
  );
  const args = [
    'vsign',
    '-s', 'https://vsign.trustasia.com',
    '--cert_from', 'csign',
    '--cert_hash', process.env.VSIGN_CERT_HASH,
    '-u', process.env.VSIGN_OPERATOR_HASH,
    '--key_pin', pin,
    '--hash', 'sha256',
    '--ts', 'TrustAsia',
    '--lang', 'zh-cn',
    '-i', input,
    '-o', output
  ];

  console.log(`[VSign] 开始签名:${path.basename(input)}`);
  try {
    const result = spawnSync(process.env.VSIGN_TOOL, args, {
      encoding: 'utf8',
      timeout: 180000,
      maxBuffer: 4 * 1024 * 1024
    });
    const log = `${result.stdout || ''}${result.stderr || ''}`
      .split(pin).join('[已隐藏]');
    if (log) process.stdout.write(log);

    if (result.error || result.status !== 0) {
      throw new Error(
        `签名工具执行失败,退出码:${result.status},错误类型:${result.error?.code || '无'}`
      );
    }
    if (!log.includes('签名成功') || log.includes('签名失败')) {
      throw new Error('未检测到签名成功信息,请检查工具输出');
    }
    if (!fs.existsSync(output) || fs.statSync(output).size === 0) {
      throw new Error('未生成有效的签名输出文件');
    }

    fs.chmodSync(output, fs.statSync(input).mode);
    fs.renameSync(output, input);
    console.log(`[VSign] 签名完成:${path.basename(input)}`);
  } finally {
    fs.rmSync(output, { force: true });
  }
};

构建时,Electron Builder 会自动调用此脚本进行签名。如脚本报错,请先根据日志排查,再重新构建。

配置 Electron Builder ​

将以下配置合并到项目 package.json 中。保留项目已有的入口、依赖、资源和其他构建配置,不要用此片段覆盖整个文件。

json
{
  "scripts": {
    "build:win": "electron-builder --win --x64 --dir",
    "build:installer": "electron-builder --win nsis --x64"
  },
  "build": {
    "win": {
      "target": "nsis",
      "signtoolOptions": {
        "sign": "./sign.cjs",
        "signingHashAlgorithms": ["sha256"]
      }
    }
  }
}

以下配置项已在 Electron Builder 26.18.0 中核对。使用其他版本时,请核对自定义签名入口的配置位置;较早版本可能使用 win.sign,应按该版本文档调整。

本示例将 signingHashAlgorithms 设为 ["sha256"],与脚本中的 SHA256 检查保持一致。如需使用其他摘要算法或多重签名,请结合签名工具和目标系统的支持情况,同时调整构建配置与签名脚本。

时间戳由自定义脚本中的 --ts TrustAsia 参数指定。如需更换时间戳服务,应修改脚本并确认当前签名工具支持该服务;仅修改 Electron Builder 内置签名工具的时间戳配置不会改变本脚本的参数。

4. 构建并执行签名 ​

先生成 Windows 应用目录,确认主程序的自动签名流程正常:

bash
npm run build:win

未自定义输出目录时,可在 dist/win-unpacked/ 中找到应用程序。成功时,日志应包含类似信息:

text
[VSign] 开始签名:您的应用.exe
vsign>开始签名, 读取文件
vsign>发送文件摘要,进行签名...
vsign>附加时间戳...
vsign>签名成功!
[VSign] 签名完成:您的应用.exe

确认应用程序签名正常后,再构建 NSIS 安装包:

bash
npm run build:installer

按本文配置,Electron Builder 会在相应的 Windows 签名步骤中调用自定义脚本;具体涉及哪些可执行文件,取决于构建目标、版本和项目配置。请检查完整构建日志,不要仅凭主程序签名成功就判断安装包也已签名。

若构建失败,请先处理错误,再重新构建。输出目录可能保留失败前生成的文件,不应将其作为正式发布产物。

接入 CI/CD ​

本地流程验证通过后,可在 macOS 构建节点按相同流程接入,并根据节点环境调整工具路径和参数。请确认:

  • 操作员证书已在实际运行构建任务的系统用户下安装,并能使用同一套云签参数完成手动签名。
  • 节点可访问 VSign 正式服务、所选时间戳服务及构建依赖下载地址。
  • 通过 CI/CD 平台的密钥管理功能注入 VSIGN_KEY_PIN,并设置其余三个环境变量。
  • 首次运行时单独验证无人值守任务,确认工具运行过程中没有等待人工处理的交互提示。
  • 用于正式发布的产物已通过完整构建及签名检查。

临时构建节点需按第 2 步准备操作员证书;本地 Mac 安装成功不代表其他节点已经完成安装。

5. 检查签名结果 ​

将生成的 Windows EXE 或安装包复制到 Windows 电脑,右键打开 属性 → 数字签名,查看签名者并进入“详细信息”,确认签名有效,同时核对时间戳和证书所属组织。

若已安装 Windows SDK SignTool,也可在能调用 signtool 的命令行中验证。将路径占位符替换为实际文件的完整路径:

powershell
signtool verify /pa /all /v /tw "<待验证的已签名EXE文件的完整路径>"

/pa 使用默认 Authenticode 验证策略,/all 检查所有签名,/v 输出详细信息,/tw 在缺少时间戳时发出警告。请分别检查应用主程序和最终安装包,并留意验证输出中的错误与警告。命令参考:Microsoft SignTool。

签名工具报告成功不等于目标 Windows 环境已完成证书信任验证。正式发布前应完成上述检查;签名后不要再修改文件内容,否则需要重新签名。

常见问题 ​

情况处理方法
提示“无该证书权限”核对 VSIGN_OPERATOR_HASH 是否来自对应云签订单的操作员记录,VSIGN_CERT_HASH 是否来自目标代码签名证书详情,并确认服务器地址正确。
提示“无法找到操作员证书”确认操作员证书已安装在运行构建的系统用户下,并检查 VSIGN_OPERATOR_HASH 是否正确。
提示 PIN 错误或密钥访问失败使用 云签详情 → 签名安全设置 中的当前 PIN,检查 VSIGN_KEY_PIN 是否已传入构建进程。
提示工具不存在或无执行权限检查 VSIGN_TOOL 的绝对路径、执行权限及处理器架构是否匹配。
手动签名成功,CI/CD 失败检查任务执行用户、操作员证书安装情况、环境变量、网络及代理设置。
构建时没有调用签名脚本检查 Electron Builder 版本、实际加载的配置文件、win.signtoolOptions.sign 和脚本路径。
时间戳失败检查时间戳服务连通性及代理、防火墙设置;完成签名和时间戳验证后再发布。
主程序签名成功,但安装包未通过验证确认执行了安装包构建,检查后续签名日志,并单独验证最终安装包。

参考文档 ​