Integration Guide

代码签名服务
接入手册

把构建产物 POST 到签名服务,拿回已签名的文件。私钥保存在签名机的 USB token 内, 不经过客户端,也不在服务端留存文件。

v1.2 · 2026-11-04 适用:Windows PE 文件(exe · dll · sys) HTTPS 接入

认证(口令)

本服务对外,所有签名请求都必须带口令。没有口令或口令错误,一律返回 401 Unauthorized,文件不会被签名。

口令形式
全公司统一的共享口令,向运维索取
携带方式
HTTP 请求头 X-Sign-Secret: <口令>
放在哪里
本机环境变量 SIGN_SECRET,或 CI 的 Secret
不要放在
代码仓库、前端代码、构建日志、聊天群公告里
bash · 最小验证
export SIGN_SECRET='<向运维索取>'

curl -X POST "https://sign.firstarpc.com/sign?ext=.exe" \
     -H "X-Sign-Secret: $SIGN_SECRET" \
     --data-binary @NovaStudio.exe \
     -o NovaStudio.signed.exe
⚠️ 口令是共享的:所有使用者共用同一个口令,日志里无法区分是谁签的。 这一点是有意为之——所以更要避免把口令写进仓库或公开渠道。
ℹ️ 运维更换口令后,旧的立即失效。届时所有构建机 / CI 都需要同步更新 SIGN_SECRET,否则会出现大批 401;网页上已登录的浏览器也会被要求重新登录。

网页登录

浏览器打开 https://sign.firstarpc.com/ 会先看到登录页,输入同一个口令即可进入签名页面。未登录时签名页面和拖拽功能都不显示, 页面内容也不会下发。

登录有效期
7 天(浏览器 cookie,HttpOnly + Secure + SameSite=Strict)
退出方式
签名页右上角「退出」
换设备
需要重新登录,登录状态不跨设备同步
接口调用
不受登录影响,仍用 X-Sign-Secret 请求头

概览

签名服务是一台插着 EV 证书 USB token 的 Windows 机器。它把你的文件交给 signtool.exe 签名并附加 GlobalSign 时间戳,然后原样返回。

Service Endpoint
https://sign.firstarpc.com

内网直连:http://<签名机>:38186(不经过公网,但为明文)

鉴权方式
请求头 X-Sign-Secret,向运维索取
请求体
文件原始字节(application/octet-stream),不是 multipart
响应体
签名后的文件原始字节
文件类型
PE 文件:exe / dll / sys
单次上限
2 GB
签名超时
180 秒(网络上传时间不计入)
并发
串行排队,多请求不会失败,只是等待
时间戳
GlobalSign TSA
ℹ️ 只签 PE 文件。驱动包的 CAB 提交(Partner Center attestation signing)不在本服务范围内。

快速开始

四步:拿到口令 → 调 /health 确认服务在线 → 带口令 POST 文件 → 保存响应。

1. 拿到口令

向运维索取统一的共享口令,存进本机环境变量:

powershell · 只做一次
[Environment]::SetEnvironmentVariable("SIGN_SECRET", "<口令>", "User")
# 重开终端后生效

2. 确认服务在线

bash
curl https://sign.firstarpc.com/health
# ok

3. 签名一个文件

bash
export SIGN_SECRET='<向运维索取>'

curl -X POST "https://sign.firstarpc.com/sign?ext=.exe" \
     -H "X-Sign-Secret: $SIGN_SECRET" \
     --data-binary @NovaStudio.exe \
     -o NovaStudio.signed.exe

4. 验证结果

cmd
signtool verify /pa /v NovaStudio.signed.exe
✓ 成功时 HTTP 200,响应体就是签名后的完整文件。看到 Successfully verified 即成功。
⚠️ 不要把口令写进代码仓库。用环境变量或 CI 的 Secret 管理,页面里填的口令只保存在自己浏览器的 localStorage。

不想写代码?用网页签

打开 https://sign.firstarpc.com/, 输入口令登录后,把文件拖进虚线框即可。签完自动下载,支持多选,多个文件会依次排队。 登录状态保持 7 天,期间不用重复输入。

ℹ️ 口令只用于换取登录 cookie,不会保存在页面里;cookie 是 HttpOnly 的, 页面脚本读不到。换浏览器或清 cookie 后需要重新登录。

API 参考

GET /health

探活接口,不需要口令,只返回服务是否在线。可用于监控和 CI 的前置检查。

状态码响应说明
200ok服务在线

POST /sign

必须携带口令,否则直接 401,请求体不会被处理。

参数位置必填说明
X-Sign-SecretHeader是 共享访问口令,缺失或错误返回 401
extQuery否 文件扩展名,默认 .exe;必须以 . 开头且不超过 10 个字符,否则按 .exe 处理
文件内容Body是 原始字节,Content-Type: application/octet-stream
状态码响应体含义 / 处理
200 签名后的文件 直接写回原路径即可
401 空 缺少或错误的 X-Sign-Secret
500 signtool 输出(文本) 签名失败:token 未插、PIN 失效、证书过期等。把这段文本发给运维
ℹ️ 签名是流式的:响应开始前请保持连接。大文件建议设置客户端超时 ≥ 10 分钟,但服务端本身的签名超时是 180 秒。

接入示例

PowerShell

powershell
$secret = $env:SIGN_SECRET

$resp = Invoke-WebRequest -Method Post `
    -Uri "https://sign.firstarpc.com/sign?ext=.exe" `
    -Headers @{ "X-Sign-Secret" = $secret } `
    -InFile ".\NovaStudio.exe" `
    -ContentType "application/octet-stream"

[IO.File]::WriteAllBytes(".\NovaStudio.signed.exe", $resp.Content)

Node.js(18+,内置 fetch)

javascript
const fs = require('node:fs');
const path = require('node:path');

async function signFile(file) {
  const ext = path.extname(file) || '.exe';
  const res = await fetch(
    `https://sign.firstarpc.com/sign?ext=${encodeURIComponent(ext)}`,
    {
      method: 'POST',
      headers: {
        'X-Sign-Secret': process.env.SIGN_SECRET,
        'Content-Type': 'application/octet-stream',
      },
      body: fs.readFileSync(file),
    },
  );

  if (!res.ok) {
    throw new Error(`签名失败 ${res.status}: ${await res.text()}`);
  }

  fs.writeFileSync(file, Buffer.from(await res.arrayBuffer()));
  return file;
}

await signFile('dist/NovaStudio.exe');

Python(requests)

python
import os
import requests

url = "https://sign.firstarpc.com/sign"
with open("NovaStudio.exe", "rb") as f:
    resp = requests.post(
        url,
        params={"ext": ".exe"},
        headers={"X-Sign-Secret": os.environ["SIGN_SECRET"]},
        data=f,
        timeout=600,
    )

resp.raise_for_status()
with open("NovaStudio.signed.exe", "wb") as f:
    f.write(resp.content)

electron-builder

在 win.json 里加入 sign 钩子。钩子会被调用多次(应用 exe、NSIS 安装包等),全部交给服务处理即可。

javascript · electron-builder win.sign
const fs = require('node:fs');
const path = require('node:path');

const sign = async ({ path: file }) => {
  const ext = path.extname(file) || '.exe';
  const res = await fetch(
    `https://sign.firstarpc.com/sign?ext=${encodeURIComponent(ext)}`,
    {
      method: 'POST',
      headers: {
        'X-Sign-Secret': process.env.SIGN_SECRET,
        'Content-Type': 'application/octet-stream',
      },
      body: fs.readFileSync(file),
    },
  );

  if (!res.ok) {
    throw new Error(`签名失败: ${await res.text()}`);
  }

  fs.writeFileSync(file, Buffer.from(await res.arrayBuffer()));
};

module.exports = sign;

CI 接入

把口令存进 CI 的 Secret(GitHub Actions 用 secrets.SIGN_SECRET), 在构建产物生成之后、上传 artifact 之前执行签名。

GitHub Actions

yaml
- name: Sign artifacts
  env:
    SIGN_SECRET: ${{ secrets.SIGN_SECRET }}
  run: |
    curl --fail --show-error \
      -X POST "https://sign.firstarpc.com/sign?ext=.exe" \
      -H "X-Sign-Secret: $SIGN_SECRET" \
      --data-binary @dist/NovaStudio.exe \
      -o dist/NovaStudio.exe.signed
    mv dist/NovaStudio.exe.signed dist/NovaStudio.exe
ℹ️ 签名机一次只处理一个请求。多个并行 job 同时签名会排队,不会失败;如果流水线对时长敏感,把签名步骤放到同一个 job 里串行执行。

常见问题

返回 401 Unauthorized

请求头 X-Sign-Secret 缺失或与签名机上的 SIGN_SECRET 不一致。 检查是否用了旧口令、环境变量是否在 CI 里正确注入,以及 header 名是否拼写正确。

返回 500,响应里有 signtool 输出

说明请求到了签名机,但签名本身失败。常见原因:USB token 未插入、token PIN 失效、 证书过期。把返回的文本原样发给运维,不要只发状态码。

请求很久没有响应 / 客户端超时

可能前面有别的签名任务在排队(服务是串行的),也可能文件很大导致上传慢。 服务端单次签名超时是 180 秒;客户端建议设置 10 分钟超时。如果持续超时,先访问 /health 看服务是否在线。

可以同时提交多个文件吗

可以。服务端会按到达顺序排队串行签名,请求不会失败,只是需要等待。 自己实现时建议也串行提交,避免在服务端堆积。

支持哪些文件类型

PE 文件:exe、dll、sys。不要传 zip、msi、脚本或驱动 CAB —— 它们不会被正确签名。 MSI 等安装包请先构建成 PE 后再签,或改用相应的签名流程。

签名后文件大小会变化吗

会变,通常增加几 KB 到几十 KB(证书链 + 时间戳)。这是正常的,不要按原始大小校验。

怎么验证签名是否有效

在 Windows 上执行:

cmd
signtool verify /pa /v NovaStudio.signed.exe

看到 Successfully verified 即成功。也可以用 Get-AuthenticodeSignature。

口令应该怎么保管

只放在个人环境变量或 CI Secret 里,不要提交到 Git,也不要贴在群里。 口令是共享的,一旦外泄请立即联系运维更换签名机上的 SIGN_SECRET, 换完之后旧口令立即失效——包括还在用它的人,所以更换后要同步通知所有构建机。

网页登录过期了 / 换台电脑还要再登录吗

需要。登录状态是保存在当前浏览器的 cookie,有效期 7 天,不会跨浏览器、跨电脑同步。 过期或清理浏览器数据后,重新打开页面输入口令即可。构建机 / CI 不使用登录,不受影响。

登录提示「尝试次数过多」

同一 IP 在 5 分钟内输错 12 次会暂停登录,等待 5 分钟后自动恢复。 请确认口令是从运维处获取的最新版本,注意不要带上多余的空格。

内网和公网地址怎么选

构建机在内网且能直连签名机时,用 http://<签名机>:38186,速度更快; 否则用 https://sign.firstarpc.com。两者接口完全一致。

支持

口令申请、token/PIN 异常、证书过期,或者这份文档没覆盖到的情况,直接找运维。

服务页面
https://sign.firstarpc.com
探活接口
GET /health(无需口令)
口令
向运维索取共享口令,填到 SIGN_SECRET
报障时请附带
HTTP 状态码、响应文本、调用时间、文件名与大小