sgcSign Server:通过 REST 实现自托管代码签名与文档签名

· 组件

代码签名存在一个密钥管理难题。常见的做法是把 .pfx 文件复制到每台需要为发布版本签名的构建代理上,或者插入 USB 令牌。私钥最终散落在那些运行不受信任构建脚本的机器上,没有任何记录说明签了什么、由谁签的,而轮换证书则意味着要逐台更动每个代理。sgcSign 2026.6 用一个全新的签名服务器解决了这个问题:一个自托管守护进程,通过 TLS REST API 对外提供签名服务,让你的流水线和开发者远程签名,而签名密钥始终只保留在一个地方。

密钥永远不会离开服务器。更妙的是,服务器可以充当硬件令牌或云 KMS 的前端,这样密钥根本不会以文件形式存在。每个请求都使用 API 密钥进行身份验证、施加速率限制,并写入防篡改的审计日志。本文将介绍该服务器能够签名的内容、如何配置、一份五分钟快速上手指南,以及调用它的两种方式:纯 curl 以及随附的 sgcsign 命令行客户端。

它能签什么

一台服务器、一套 API、八种签名格式。同样的端点形态(POST /api/v1/sign/<format>)同时涵盖可执行文件和文档:

每种格式都接受一个可选的 RFC 3161 时间戳 URL,这样即使签名证书过期,签名仍然有效。配套的 POST /api/v1/verify 端点可校验现有签名,并返回签名者主题、有效性和时间戳。

可插拔的密钥提供程序

提供程序(provider)是指向某个签名密钥的具名句柄。调用方在每个请求中指定提供程序的名称,但永远看不到密钥本身。你可以根据需要注册任意多个,同一台服务器既可以用本地 PFX 为内部工具签名,又可以用云端支持的 EV 证书为公开发布版本签名。共支持九种提供程序类型:

机密信息不会留在配置文件里。任何名称以 _env 结尾的参数都从环境变量读取,因此 PFX 密码、令牌 PIN 或云端机密都由服务运行环境提供,而不会提交到磁盘。

配置服务器

整个服务器由一个 JSON 文件驱动,即 sgcSignServer.conf.json(随附一份带注释的示例 sgcSignServer.conf.sample.json)。顶层结构由若干个块组成:

{
  "server": {
    "listen": "0.0.0.0",
    "port": 8443,
    "tls": {
      "enabled": true,
      "cert_file": "certs/server.crt",
      "key_file":  "certs/server.key"
    },
    "max_upload_mb": 512,
    "firewall": {
      "enabled": true,
      "whitelist": ["10.0.0.0/8"],
      "brute_force": { "enabled": true, "max_attempts": 5, "ban_duration_sec": 900 },
      "rate_limit":  { "enabled": true, "max_connections_per_ip": 30, "time_window_sec": 60 }
    }
  },
  "storage": { "sqlite_path": "data/sgcsignserver.db" },
  "admin": {
    "initial_user": "admin",
    "initial_password_env": "SGCSIGN_ADMIN_INIT_PW",
    "session_timeout_minutes": 60
  },
  "audit": { "retention_days": 365 },
  "providers": [
    {
      "name": "pfx-build",
      "type": "PFX",
      "params": { "file": "certs/build.pfx", "password_env": "SGCSIGN_PFX_PW" }
    },
    {
      "name": "ev-release",
      "type": "AzureTS",
      "params": {
        "tenant_id": "00000000-0000-0000-0000-000000000000",
        "client_id": "00000000-0000-0000-0000-000000000000",
        "client_secret_env": "SGCSIGN_AZURE_TS_SECRET",
        "account": "your-account",
        "certificate_profile": "your-profile",
        "endpoint": "https://eus.codesigning.azure.net/"
      }
    }
  ]
}

这些块直接对应各项功能。server 设置绑定地址、端口(默认 8443)、TLS 证书以及内置防火墙(IP 允许和拒绝列表、暴力破解锁定、连接速率限制、路径遍历和载荷防护)。storage 指向保存用户、API 密钥、审计日志和 webhook 队列的 SQLite 数据库。admin 定义首个用户以及会话的有效时长。audit 设置保留时间窗口。providers 就是上面列出的那个列表。

引导用的管理员密码绝不会存储在文件中。在第一次启动、用户表为空时,服务器会读取由 initial_password_env(默认 SGCSIGN_ADMIN_INIT_PW)命名的环境变量,并创建管理员账户。在之后的每次启动中,该变量都会被忽略,因此它只用于初始化首次登录。

五分钟快速上手

安装服务器(安装向导会注册 Windows 服务,或运行 sgcSignServer.exe --install),然后:

1. 创建一个测试用代码签名证书并将其导出为 PFX。在真实部署中,你会导入由 CA 签发的证书,或者跳过此步,改为让提供程序指向你的令牌或云 KMS。

$cert = New-SelfSignedCertificate -Type CodeSigningCert `
  -Subject "CN=sgcSign Quickstart" -KeyAlgorithm RSA -KeyLength 3072 `
  -CertStoreLocation Cert:\CurrentUser\My -NotAfter (Get-Date).AddYears(1)
$pw = ConvertTo-SecureString "QuickStartPFX!" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -Password $pw `
  -FilePath "C:\Program Files\sgcSign Server\certs\quickstart.pfx"

2. 将机密设置为计算机级环境变量,以便服务账户能够读取它们:

setx /M SGCSIGN_ADMIN_INIT_PW "ChangeMeNow!"
setx /M SGCSIGN_PFX_PW        "QuickStartPFX!"

3. 编写一份最小化配置,只含一个 PFX 提供程序(仅供 localhost 测试,关闭 TLS):

{
  "server":   { "listen": "127.0.0.1", "port": 8443, "tls": { "enabled": false } },
  "storage":  { "sqlite_path": "data/sgcsignserver.db" },
  "admin":    { "initial_user": "admin", "initial_password_env": "SGCSIGN_ADMIN_INIT_PW" },
  "audit":    { "retention_days": 365 },
  "providers": [
    { "name": "pfx-quickstart", "type": "PFX",
      "params": { "file": "certs/quickstart.pfx", "password_env": "SGCSIGN_PFX_PW" } }
  ]
}

4. 启动服务并确认它正在监听:

sc start sgcSignServer
sc query sgcSignServer

如果它在启动时停止,请用 sgcSignServer.exe --console --config <path> 在前台运行,以便实时查看错误(常见原因是缺少 SGCSIGN_ADMIN_INIT_PW 或 PFX 不可读)。--selftest-providers 标志会加载配置、初始化每个提供程序,然后退出,因此你可以在正式上线前验证密钥。

5. 登录并创建 API 密钥。打开 http://localhost:8443/admin,用引导密码以 admin 身份登录,并创建一个 API 密钥。明文密钥在创建时只显示一次;请立即复制。密钥带有前缀 sgcsk_

6. 用 curl 为一个测试可执行文件签名

curl -X POST http://localhost:8443/api/v1/sign/authenticode ^
  -H "X-API-Key: sgcsk_..." ^
  -F file=@app.exe ^
  -F provider=pfx-quickstart ^
  -F hash=sha256 ^
  --output app-signed.exe

整个流程就是这样:配置一个提供程序、签发一个密钥、进行签名。从这里开始,你可以把自签名 PFX 换成真实证书,开启 TLS,并把防火墙收紧到只允许你的 CI 子网。

通过 REST 进行签名

每个签名请求都是一个携带文件和若干表单字段的 POST。身份验证只需一个请求头,可以是 X-API-Key: sgcsk_...Authorization: Bearer sgcsk_...。响应是作为 application/octet-stream 返回的已签名工件,其中签名者主题和签名耗时分别在 X-Sgcsign-Signer-SubjectX-Sgcsign-Duration-Ms 请求头中返回。

为一个 PDF 签名,并带上可见的签名原因和地点:

curl -X POST https://sign.acme.local:8443/api/v1/sign/pades \
  -H "X-API-Key: sgcsk_..." \
  -F file=@invoice.pdf \
  -F provider=qualified-eu \
  -F tsa_url=http://timestamp.digicert.com \
  -F reason=Approved \
  -F location="Madrid, Spain" \
  -F signer_name="Acme Billing" \
  -o invoice-signed.pdf

用 eIDAS XAdES 配置文件为一个 XML 发票签名:

curl -X POST https://sign.acme.local:8443/api/v1/sign/xades \
  -H "X-API-Key: sgcsk_..." \
  -F file=@invoice.xml \
  -F provider=qualified-eu \
  -F profile=eidas \
  -F xades_type=enveloped \
  -o invoice-signed.xml

验证签名时返回的是 JSON 而非文件:

curl -X POST https://sign.acme.local:8443/api/v1/verify \
  -H "X-API-Key: sgcsk_..." \
  -F file=@app-signed.exe \
  -F format=authenticode

{ "valid": true, "status": "valid",
  "subject": "CN=Acme Inc., O=Acme, C=US",
  "has_timestamp": true, "timestamp_time": "2026-06-15T13:01:17.000Z" }

sgcsign 命令行客户端

服务器随附 sgcsign,这是一个小巧的跨格式 CLI,带有四个动词:signverifykeyshealth。服务器 URL、API 密钥和提供程序既可以来自命令行标志,也可以来自环境变量 SGCSIGN_SERVERSGCSIGN_APIKEYSGCSIGN_PROVIDER,从而让机密在 CI 中不会出现在命令行上:

set SGCSIGN_SERVER=https://sign.acme.local:8443
set SGCSIGN_APIKEY=sgcsk_...

sgcsign sign -f authenticode -p pfx-build -o app-signed.exe app.exe ^
  --tsa http://timestamp.digicert.com --desc "Acme Installer"

sgcsign verify -f authenticode app-signed.exe

对于 Authenticode,还有一个节省带宽的技巧。在预哈希模式下(--prehash,默认开启),CLI 会在本地计算 PE 哈希并仅上传该哈希,也就是几百字节,而不是数兆字节的二进制文件。服务器对哈希进行签名并返回一个分离式 PKCS#7 数据块,客户端再把它嵌入文件。在带宽受限的 CI 代理上为一个大型安装程序签名时,这就把数兆字节的上传变成了一次极小的上传。

安全与运维

该服务器被设计为在真实签名密钥前无人值守地运行,因此运维层面的考量与加密本身同样重要:

为何要自托管

该服务器的意义在于,把一大批高风险、持有密钥的构建代理收敛为单个经过加固的端点。签名密钥只存在于服务器上,或存在于服务器所代理的 HSM 或云 KMS 中,永远不会落到运行你构建脚本的机器上。访问由带配额的每密钥 API 令牌把关,并限定到具体项目,而每一个签名都记录在你自己掌控的审计日志中。轮换证书、吊销某个已失陷代理的密钥,或者证明谁签了什么,都从原先的多处排查变成了只需看一处。

供应情况

sgcSign Server 是 sgcSign 2026.6.0 的一部分。它可作为 Windows 服务或控制台应用程序运行,由上面展示的那个单一 JSON 文件配置,并可通过 curl、随附的 sgcsign CLI 或任何借助其 OpenAPI 描述的 HTTP 客户端来驱动。

有疑问、反馈,或需要帮助完成搭建?联系我们。你将收到来自编写这些代码的人的回复。