sgcSign Server: firma de código y firma de documentos autoalojada sobre REST

· Componentes

La firma de código tiene un problema de gestión de claves. El montaje habitual copia un archivo .pfx, o conecta un token USB, en cada agente de compilación que necesita firmar una versión. La clave privada acaba en máquinas que ejecutan scripts de compilación no confiables, no hay registro de qué se firmó ni quién lo firmó, y rotar el certificado implica tocar cada agente. sgcSign 2026.6 responde a esto con un nuevo servidor de firma: un demonio autoalojado que expone la firma a través de una API REST sobre TLS, de modo que tus pipelines y desarrolladores firman de forma remota mientras la clave de firma permanece en un único lugar.

La clave nunca sale del servidor. Mejor aún, el servidor puede actuar como fachada de un token de hardware o de un KMS en la nube, de modo que la clave nunca existe como archivo. Cada petición se autentica con una clave de API, está sujeta a límites de tasa y se escribe en un registro de auditoría a prueba de manipulaciones. Esta publicación cubre qué puede firmar el servidor, cómo configurarlo, un inicio rápido de cinco minutos y las dos formas de invocarlo: curl simple y el cliente de línea de comandos sgcsign incluido.

Qué firma

Un servidor, una API, ocho formatos de firma. La misma forma de endpoint (POST /api/v1/sign/<format>) cubre tanto ejecutables como documentos:

Cada formato acepta una URL de sello de tiempo RFC 3161 opcional, de modo que las firmas siguen siendo válidas después de que el certificado de firma caduque. Un endpoint complementario POST /api/v1/verify comprueba una firma existente y devuelve el subject del firmante, la validez y el sello de tiempo.

Proveedores de claves conectables

Un proveedor es un identificador con nombre de una clave de firma. El invocador nombra un proveedor en cada petición; nunca ve la clave. Puedes registrar tantos como necesites, y el mismo servidor puede combinar un PFX local para herramientas internas con un certificado EV respaldado en la nube para versiones públicas. Se admiten nueve tipos de proveedor:

Los secretos permanecen fuera del archivo de configuración. Cualquier parámetro cuyo nombre termine en _env se lee de una variable de entorno, de modo que la contraseña del PFX, el PIN del token o el secreto en la nube los proporciona el entorno del servicio, sin quedar guardados en disco.

Configuración del servidor

Todo el servidor se gobierna mediante un único archivo JSON, sgcSignServer.conf.json (se incluye un ejemplo documentado como sgcSignServer.conf.sample.json). La forma de nivel superior es un puñado de bloques:

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

Los bloques se corresponden directamente con funcionalidades. server establece la dirección de escucha, el puerto (8443 por defecto), el certificado TLS y el firewall integrado (listas de IP permitidas y denegadas, bloqueo por fuerza bruta, limitación de tasa de conexiones, protecciones contra path-traversal y contra contenidos maliciosos). storage apunta a la base de datos SQLite que contiene los usuarios, las claves de API, el registro de auditoría y la cola de webhooks. admin define el primer usuario y cuánto duran las sesiones. audit establece la ventana de retención. providers es la lista anterior.

La contraseña de administrador de arranque nunca se almacena en el archivo. En el primer arranque, cuando la tabla de usuarios está vacía, el servidor lee la variable de entorno indicada por initial_password_env (por defecto SGCSIGN_ADMIN_INIT_PW) y crea la cuenta de administrador. En cada arranque posterior esa variable se ignora, de modo que solo sirve para sembrar el primer inicio de sesión.

Inicio rápido en cinco minutos

Instala el servidor (el asistente de configuración registra el servicio de Windows, o ejecuta sgcSignServer.exe --install), y luego:

1. Crea un certificado de firma de código de prueba y expórtalo como PFX. Para una implementación real importas tu certificado emitido por una CA, o te saltas este paso y apuntas un proveedor a tu token o a tu KMS en la nube.

$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. Establece los secretos como variables de entorno de máquina para que la cuenta del servicio pueda leerlos:

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

3. Escribe una configuración mínima con un proveedor PFX (TLS desactivado solo para una prueba en localhost):

{
  "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. Inicia el servicio y confirma que está escuchando:

sc start sgcSignServer
sc query sgcSignServer

Si se detiene al arrancar, ejecútalo en primer plano con sgcSignServer.exe --console --config <path> para ver el error en vivo (una SGCSIGN_ADMIN_INIT_PW ausente o un PFX que no se puede leer son las causas habituales). El indicador --selftest-providers carga la configuración, inicializa cada proveedor y termina, de modo que puedes validar las claves antes de ponerlo en producción.

5. Inicia sesión y crea una clave de API. Abre http://localhost:8443/admin, inicia sesión como admin con la contraseña de arranque y crea una clave de API. La clave en texto plano se muestra una sola vez en el momento de su creación; cópiala de inmediato. Las claves llevan el prefijo sgcsk_.

6. Firma un ejecutable de prueba con 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

Ese es el ciclo completo: configurar un proveedor, emitir una clave, firmar. A partir de aquí sustituyes el PFX autofirmado por un certificado real, activas TLS y bloqueas el firewall para limitarlo a la subred de tu CI.

Firma sobre REST

Cada petición de firma es un POST con el archivo y unos pocos campos de formulario. La autenticación es una sola cabecera, ya sea X-API-Key: sgcsk_... o Authorization: Bearer sgcsk_.... La respuesta es el artefacto firmado como application/octet-stream, con el subject del firmante y la duración de la firma devueltos en las cabeceras X-Sgcsign-Signer-Subject y X-Sgcsign-Duration-Ms.

Firma de un PDF, con un motivo y una ubicación de firma visibles:

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

Firma de una factura XML con un perfil XAdES de eIDAS:

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

La verificación de una firma devuelve JSON en lugar de un archivo:

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

El cliente de línea de comandos sgcsign

El servidor incluye sgcsign, una pequeña CLI multiformato con cuatro verbos: sign, verify, keys y health. La URL del servidor, la clave de API y el proveedor pueden provenir de indicadores o de las variables de entorno SGCSIGN_SERVER, SGCSIGN_APIKEY y SGCSIGN_PROVIDER, lo que mantiene los secretos fuera de la línea de comandos en 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

Para Authenticode hay un truco que ahorra ancho de banda. Con el modo de prehash (--prehash, activado por defecto) la CLI calcula el hash del PE localmente y sube solo el hash, unos pocos cientos de bytes en lugar de un binario de varios megabytes. El servidor firma el hash y devuelve un blob PKCS#7 detached, que el cliente incrusta en el archivo. En un agente de CI con ancho de banda limitado que firma un instalador grande, eso convierte una subida de varios megabytes en una diminuta.

Seguridad y operación

El servidor está diseñado para funcionar sin supervisión delante de una clave de firma real, así que la superficie operativa importa tanto como la criptografía:

Por qué autoalojar

El objetivo del servidor es reducir una flota de agentes de compilación arriesgados, portadores de claves, a un único endpoint reforzado. La clave de firma reside únicamente en el servidor, o en el HSM o KMS en la nube del que el servidor hace de fachada, nunca en las máquinas que ejecutan tus scripts de compilación. El acceso está controlado por tokens de API por clave con cuotas, acotados a proyectos, y cada firma queda registrada en un registro de auditoría que tú controlas. Rotar un certificado, revocar la clave de un agente comprometido o demostrar quién firmó qué se convierten en un único lugar donde mirar en vez de muchos.

Disponibilidad

El sgcSign Server forma parte de sgcSign 2026.6.0. Funciona como servicio de Windows o como aplicación de consola, se configura mediante el único archivo JSON mostrado arriba y se gobierna desde curl, la CLI sgcsign incluida o cualquier cliente HTTP a través de su descripción OpenAPI.

¿Preguntas, comentarios o ayuda para ponerlo en marcha? Ponte en contacto. Recibirás respuesta de las personas que escribieron el código.