Skip to content

oomol-lab/sign-server

 
 

Repository files navigation

Sign Server

English | 简体中文

An HTTP wrapper around Microsoft SignTool.exe that lets you sign Windows binaries from any machine on your LAN — including Linux/macOS CI runners that cannot access the hardware UKey directly.

Warning

This service exposes a hardware-backed code-signing certificate over HTTP. Do not expose it to the public internet. Run it only on a trusted intranet, and protect it with the built-in HTTP Basic Auth (see Configure Authentication).

Features

  • HTTP API for signing files with a hardware UKey on a remote Windows host
  • Content-addressed cache to skip re-uploading unchanged binaries
  • Chunked upload for large files — bypass gateway size limits and retry failed chunks
  • HTTP Basic Auth with multi-account support
  • Built-in Web UI for manual signing and connectivity testing
  • Drop-in sign.js for Electron Builder

Requirements

  • Windows host with the code-signing UKey physically connected
  • Bun
  • SignTool.exe (ships with the Windows SDK)

1. Prepare the Windows Machine

  1. Install the UKey driver (e.g. SafeNet).

    • In the driver client settings, enable "Enable single logon".
  2. Install the certificate (e.g. via DigiCertHardwareCertificateInstaller).

    • Make sure the certificate is also installed into the local certificate store.
  3. Verify the setup in PowerShell:

    gci -Recurse Cert: -CodeSigningCert

    The certificates installed in steps 1 and 2 should appear. If not, confirm the UKey is connected and try again.

  4. Install Bun.

  5. Install SignTool.exe from the Windows SDK installer.

Note

Some of the steps above cannot be completed over Windows Remote Desktop.

2. Configure Authentication

Create a .token file (already in .gitignore) before starting the service:

cp .token.example .token
# edit .token, fill in username:password

File format. Each non-empty, non-comment line is parsed as a username:password pair. Any matching pair grants access; multiple accounts are supported. All endpoints — including the Web UI — require authentication.

# .token
admin:s3cret
ci-bot:another-secret

3. Configure SignTool & Certificate

Create a .config file (already in .gitignore):

cp .config.example .config
# edit .config, fill in signtool path and certificate thumbprint

File format. key=value per line; lines starting with # are comments. Both keys are optional — leave a value empty to fall back to auto-detection (signtool searches the standard Windows SDK install paths; thumbprint is only required when the machine has more than one code-signing certificate).

# .config
signtool=C:/Program Files (x86)/Windows Kits/10/bin/x64/signtool.exe
thumbprint=ABCDEF0123456789ABCDEF0123456789ABCDEF01

4. Start the Service

git clone https://github.com/netless-io/sign-server
cd sign-server
bun start
# serving http://192.0.2.10:3000

Take note of the URL printed above — that's your SIGN_SERVER_URL. The host can be either an IP address or a DNS name (e.g. http://signer.intranet:3000); you'll need it when integrating with Electron Builder (see Section 5).

If bun start reports an error, see the table below.

Troubleshooting

Error Resolution
Not found .token file Create a .token file in the project root following the format of .token.example.
Invalid .token file Ensure each entry in .token follows the username:password format.
Not found .config file Create a .config file in the project root following the format of .config.example.
Not found SignTool.exe Set signtool in .config to the absolute path of SignTool.exe.
Not found certificate Re-check Section 1 and confirm the UKey is connected.
Found multiple certificates Set thumbprint in .config to the 40-char thumbprint of the certificate to use. Run gci -Recurse Cert: -CodeSigningCert | Select Subject,Thumbprint to list candidates.

Web UI

Visit the URL printed at startup in a browser to access the built-in Web UI, which provides a minimal upload-and-sign interface. The browser will prompt for the credentials defined in .token. Use it to verify the end-to-end signing flow before integrating with your build pipeline.

5. Integrate with Electron Builder

Refer to the official documentation on custom signing for context.

sign.example.js is a drop-in custom sign script. Wire it up in your electron-builder config:

{
  "win": { "sign": "./sign.example.js" }
}

The script is fully configured via environment variables — no code changes required:

SIGN_SERVER_URL=http://signer.intranet:3000 \
SIGN_SERVER_USER=admin \
SIGN_SERVER_PASS=secret \
  electron-builder ...

SIGN_SERVER_URL accepts either an IP address or a DNS name.

Note

The sample script uses the native fetch() API, so electron-builder must run under Node.js 18 or newer. On older versions, import { fetch, FormData } from undici.

API Reference

All endpoints require HTTP Basic Auth.

POST /exists

Check whether a file with the given content hash is already cached on the server.

  • Body — raw text: the file's content hash
  • Response — JSON true | false

POST /sign

Sign a file and return the signed bytes.

  • Bodymultipart/form-data:
    • file — either a hash string (cache hit) or the file blob
    • hash"sha1" or "sha256"
    • isNest"1" for nested signatures, "" otherwise
  • Responseapplication/octet-stream: the signed file

Chunked Upload

For large files: split the payload into smaller pieces, upload them independently to bypass gateway request-size limits, and retry per-chunk on failure. Once all chunks are uploaded, call /sign with the file hash — no re-upload needed.

Pending chunks are cleared when the server restarts; resumable upload only works within a single server lifetime.

sign.example.js enables chunked upload automatically for files larger than 16 MiB, with 4 MiB chunks. Override via the SIGN_SERVER_CHUNK_THRESHOLD / SIGN_SERVER_CHUNK_SIZE environment variables.

POST /chunk/status

Query which chunks have been received for the given file hash, for resuming an interrupted upload.

  • Body — raw text: the final file's MD5 hash
  • Response — JSON { "received": number[], "total": number | null, "name": string | null }

POST /chunk

Upload a single chunk.

  • Query parameters:
    • hash — final file's MD5 hash
    • index — zero-based chunk index
    • total — total number of chunks
    • name — original file name (URL-encoded)
  • Bodyapplication/octet-stream: the chunk bytes
  • Response — JSON { "received": number[] }

POST /chunk/finalize

Assemble all uploaded chunks into the final file and verify its MD5 matches the supplied hash. On success the file is added to the cache; subsequent calls to /sign can reference it by hash.

  • Body — raw text: the final file's MD5 hash
  • Response200 { "ok": true }; 400 with an error message if any chunk is missing or the hash does not match

POST /chunk/abort

Discard the in-progress chunked upload and delete any chunks already received.

  • Body — raw text: the final file's MD5 hash
  • Response — JSON { "ok": true }

SignTool.exe Cheatsheet

signtool sign
  /debug /td sha256 /tr http://timestamp.digicert.com /as
  /fd {hash} /sha1 {thumbprint} /s {store} /sm
  {file.exe}

References

License

MIT

About

SignTool.exe as a server.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • TypeScript 80.1%
  • JavaScript 15.5%
  • HTML 4.4%