sgcSign Server: zelf-gehost code-ondertekenen en documentondertekenen via REST

· Componenten

Code-ondertekenen heeft een sleutelbeheerprobleem. De gebruikelijke opzet kopieert een .pfx-bestand, of steekt een USB-token, in elke build-agent die een release moet ondertekenen. De privésleutel belandt op machines die niet-vertrouwde build-scripts draaien, er is geen registratie van wat er is ondertekend of door wie, en het roteren van het certificaat betekent dat je elke agent moet aanraken. sgcSign 2026.6 beantwoordt dit met een nieuwe ondertekeningsserver: een zelf-gehoste daemon die ondertekenen aanbiedt via een TLS REST API, zodat je pijplijnen en ontwikkelaars op afstand ondertekenen terwijl de ondertekeningssleutel op precies één plek blijft.

De sleutel verlaat nooit de server. Sterker nog, de server kan een hardware-token of een cloud-KMS afschermen, zodat de sleutel helemaal nooit als bestand bestaat. Elke aanvraag wordt geauthenticeerd met een API-sleutel, voorzien van rate-limiting, en weggeschreven naar een tamper-evident auditlog. Deze post behandelt wat de server kan ondertekenen, hoe je hem configureert, een snelstart van vijf minuten, en de twee manieren om hem aan te roepen: gewoon curl en de meegeleverde sgcsign command-line client.

Wat het ondertekent

Eén server, één API, acht handtekeningformaten. Dezelfde endpoint-vorm (POST /api/v1/sign/<format>) dekt zowel uitvoerbare bestanden als documenten:

Elk formaat accepteert een optionele RFC 3161 timestamp-URL, zodat handtekeningen geldig blijven nadat het ondertekeningscertificaat is verlopen. Een bijbehorend POST /api/v1/verify endpoint controleert een bestaande handtekening en geeft het onderwerp van de ondertekenaar, de geldigheid en de timestamp terug.

Pluggable sleutelproviders

Een provider is een benoemde verwijzing naar een ondertekeningssleutel. De aanroeper noemt een provider bij elke aanvraag; hij ziet de sleutel nooit. Je kunt er zoveel registreren als je nodig hebt, en dezelfde server kan een lokale PFX voor interne tools combineren met een cloud-ondersteund EV-certificaat voor publieke releases. Negen providertypes worden ondersteund:

Geheimen blijven buiten het configuratiebestand. Elke parameter waarvan de naam eindigt op _env wordt gelezen uit een omgevingsvariabele, zodat het PFX-wachtwoord, de token-PIN of het cloud-geheim wordt aangeleverd door de service-omgeving en niet op schijf wordt vastgelegd.

De server configureren

De hele server wordt aangestuurd door één JSON-bestand, sgcSignServer.conf.json (een gedocumenteerd voorbeeld wordt meegeleverd als sgcSignServer.conf.sample.json). De structuur op het hoogste niveau bestaat uit een handvol blokken:

{
  "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/"
      }
    }
  ]
}

De blokken komen rechtstreeks overeen met functies. server stelt het bind-adres, de poort (8443 standaard), het TLS-certificaat en de ingebouwde firewall in (IP-toelaat- en weigerlijsten, brute-force-vergrendeling, connectie-rate-limiting, en bescherming tegen path-traversal en payloads). storage wijst naar de SQLite-database die gebruikers, API-sleutels, het auditlog en de webhook-wachtrij bevat. admin definieert de eerste gebruiker en hoe lang sessies duren. audit stelt het bewaarvenster in. providers is de bovenstaande lijst.

Het bootstrap-beheerderswachtwoord wordt nooit in het bestand opgeslagen. Bij de allereerste start, wanneer de gebruikerstabel leeg is, leest de server de omgevingsvariabele die door initial_password_env wordt benoemd (standaard SGCSIGN_ADMIN_INIT_PW) en maakt het beheerdersaccount aan. Bij elke latere start wordt die variabele genegeerd, zodat hij alleen ooit de eerste login initialiseert.

Snelstart in vijf minuten

Installeer de server (de installatiewizard registreert de Windows-service, of voer sgcSignServer.exe --install uit), en daarna:

1. Maak een test code-ondertekeningscertificaat en exporteer het als een PFX. Voor een echte implementatie importeer je je door een CA uitgegeven certificaat, of sla je dit over en wijs je in plaats daarvan een provider naar je token of cloud-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. Stel de geheimen in als machine-omgevingsvariabelen zodat het serviceaccount ze kan lezen:

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

3. Schrijf een minimale config met één PFX-provider (TLS uit, uitsluitend voor een localhost-test):

{
  "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. Start de service en bevestig dat hij luistert:

sc start sgcSignServer
sc query sgcSignServer

Als hij bij het starten stopt, voer hem dan op de voorgrond uit met sgcSignServer.exe --console --config <path> om de fout live te zien (een ontbrekende SGCSIGN_ADMIN_INIT_PW of een onleesbare PFX zijn de gebruikelijke oorzaken). De vlag --selftest-providers laadt de config, initialiseert elke provider en sluit af, zodat je sleutels kunt valideren voordat je live gaat.

5. Log in en maak een API-sleutel aan. Open http://localhost:8443/admin, meld je aan als admin met het bootstrap-wachtwoord, en maak een API-sleutel aan. De platte-tekstsleutel wordt eenmalig getoond bij het aanmaken; kopieer hem direct. Sleutels dragen de prefix sgcsk_.

6. Onderteken een test-uitvoerbaar bestand met 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

Dat is de hele cyclus: configureer een provider, geef een sleutel uit, onderteken. Vanaf hier vervang je de zelf-ondertekende PFX door een echt certificaat, schakel je TLS in, en vergrendel je de firewall tot je CI-subnet.

Ondertekenen via REST

Elke ondertekeningsaanvraag is een POST met het bestand en een paar formuliervelden. Authenticatie is één header, ofwel X-API-Key: sgcsk_... of Authorization: Bearer sgcsk_.... Het antwoord is het ondertekende artefact als application/octet-stream, waarbij het onderwerp van de ondertekenaar en de ondertekeningsduur worden teruggegeven in de headers X-Sgcsign-Signer-Subject en X-Sgcsign-Duration-Ms.

Een PDF ondertekenen, met een zichtbare ondertekeningsreden en -locatie:

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

Een XML-factuur ondertekenen met een eIDAS XAdES-profiel:

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

Een handtekening verifiëren geeft JSON terug in plaats van een bestand:

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" }

De sgcsign command-line client

De server wordt geleverd met sgcsign, een kleine cross-format CLI met vier werkwoorden: sign, verify, keys en health. De server-URL, API-sleutel en provider kunnen via vlaggen komen of via de omgevingsvariabelen SGCSIGN_SERVER, SGCSIGN_APIKEY en SGCSIGN_PROVIDER, wat geheimen buiten de command-line in CI houdt:

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

Voor Authenticode is er een bandbreedtebesparende truc. Met pre-hash-modus (--prehash, standaard aan) berekent de CLI de PE-hash lokaal en uploadt alleen de hash, een paar honderd bytes in plaats van een binary van meerdere megabytes. De server ondertekent de hash en geeft een detached PKCS#7-blob terug, die de client in het bestand inbedt. Op een CI-agent met beperkte bandbreedte die een grote installer ondertekent, verandert dat een upload van meerdere megabytes in een minuscule.

Beveiliging en operationeel beheer

De server is gebouwd om onbeheerd te draaien voor een echte ondertekeningssleutel, dus het operationele oppervlak is even belangrijk als de crypto:

Waarom zelf hosten

Het doel van de server is om een vloot van risicovolle, sleutel-dragende build-agents terug te brengen tot één gehard endpoint. De ondertekeningssleutel leeft alleen op de server, of in de HSM of cloud-KMS die de server afschermt, nooit op de machines die je build-scripts draaien. Toegang wordt afgeschermd door API-tokens per sleutel met quota, afgestemd op projecten, en elke handtekening wordt vastgelegd in een auditlog dat jij beheert. Een certificaat roteren, de sleutel van een gecompromitteerde agent intrekken, of bewijzen wie wat heeft ondertekend, worden allemaal één plek om te kijken in plaats van vele.

Beschikbaarheid

De sgcSign Server maakt deel uit van sgcSign 2026.6.0. Hij draait als een Windows-service of een console-applicatie, wordt geconfigureerd door het ene JSON-bestand dat hierboven is getoond, en wordt aangestuurd vanuit curl, de meegeleverde sgcsign CLI, of elke HTTP-client via zijn OpenAPI-beschrijving.

Vragen, feedback of hulp bij het opzetten? Neem contact op. Je krijgt een antwoord van de mensen die de code hebben geschreven.