Saltar al contenido
ecdsa.com
← Todos los artículos

Firma Authenticode: claves, sellos de tiempo, verificación

Firmamos un pequeño ejecutable de Windows con osslsigncode 2.14 y un sello de tiempo de DigiCert, y después lo sometimos a tres pruebas: una copia firmada sin sello de tiempo, un byte de código modificado y datos ocultos en la tabla de certificados. Las reglas proceden de los Code Signing Baseline Requirements 3.11.0 del CA/Browser Forum y de la propia documentación de Microsoft, consultados en octubre de 2026.

Respuesta breve. Authenticode incrusta un SignedData PKCS #7 en una tabla al final de un archivo PE de Windows; la firma cubre un hash del archivo que omite la suma de comprobación, el puntero a esa tabla y la propia tabla. Desde el 1 de junio de 2023, la clave de un certificado de firma de código de confianza pública debe generarse y usarse en un módulo de hardware (un token, un HSM local o un servicio de HSM en la nube con registro de auditoría completo), así que el habitual archivo .pfx en un servidor de compilación ya no te permite obtener un certificado. Los certificados emitidos desde el 1 de marzo de 2026 duran como máximo 460 días, y Artifact Signing de Microsoft emite certificados que duran 72 horas, lo que convierte el sello de tiempo RFC 3161 en la pieza que mantiene válida la firma: firma siempre con /fd SHA256 /tr <TSA URL> /td SHA256. Los certificados EV ya no garantizan una reputación inmediata en SmartScreen. Verifica con signtool verify /pa /tw en Windows o con osslsigncode verify en otros sistemas.

Qué se firma

Un archivo PE (.exe, .dll, .sys) tiene un directorio de datos en su encabezado opcional; la entrada 4, la tabla de certificados (Certificate Table), guarda el desplazamiento en el archivo y el tamaño de una lista de estructuras WIN_CERTIFICATE añadidas al final del archivo. En Authenticode, esa entrada tiene la revisión 0x0200 y el tipo WIN_CERT_TYPE_PKCS_SIGNED_DATA (0x0002), y contiene un SignedData PKCS #7 (formato PE).

Lo que se firma no es el archivo, sino una estructura SpcIndirectDataContent que contiene el hash del archivo. Ese hash no puede abarcar la propia firma, así que, en palabras de Microsoft, «debe excluir ciertos campos de la imagen PE, como la suma de comprobación y la entrada Certificate Table de los directorios de datos del encabezado opcional», además de la tabla de certificados. Todo lo demás —encabezados, secciones y datos posteriores a la última sección— entra en el hash.

Nuestra muestra es un programa de 1,6 MB compilado de forma cruzada con Go 1.27.3 (GOOS=windows GOARCH=amd64). Un breve script de Python lee la entrada de la tabla de certificados antes y después de firmar:

python3 pe.py hello.exe show; python3 pe.py hello-signed.exe show
file size 1651712, certificate table at offset 0, size 0
file size 1660952, certificate table at offset 1651712, size 9240
WIN_CERTIFICATE: dwLength 9240, wRevision 0x0200, wCertificateType 0x0002

Los 9240 bytes son la firma, la cadena de certificados y el token de sello de tiempo. Al listar los identificadores de objeto de la firma extraída aparecen las partes propias de Authenticode junto a las habituales de CMS:

openssl asn1parse -inform DER -in sig.der | grep -o 'OBJECT *:[^ ]*' | sort | uniq -c (extracto)
      1 OBJECT            :1.3.6.1.4.1.311.2.1.11
      1 OBJECT            :1.3.6.1.4.1.311.2.1.12
      1 OBJECT            :1.3.6.1.4.1.311.2.1.15
      2 OBJECT            :1.3.6.1.4.1.311.2.1.4
      1 OBJECT            :1.3.6.1.4.1.311.3.3.1
      2 OBJECT            :id-smime-ct-TSTInfo
      2 OBJECT            :messageDigest
      2 OBJECT            :signingTime

1.3.6.1.4.1.311.2.1.4 es SpcIndirectDataContent; …2.1.15, los datos de la imagen PE que contiene; …2.1.11 y …2.1.12, el tipo de declaración y el nombre y la URL del programa, y 1.3.6.1.4.1.311.3.3.1, el token de sello de tiempo RFC 3161 guardado como atributo no firmado, cuyo tipo de contenido es id-smime-ct-TSTInfo.

Certificados y claves en 2026

Los certificados de firma de código de confianza pública se rigen por los Code Signing Baseline Requirements (CSBR) del CA/Browser Forum, versión 3.11.0 del 16 de junio de 2026. Estas son las reglas que cambian tu proceso de compilación:

  • Claves en hardware para todos. «A partir del 1 de junio de 2023», la clave privada del suscriptor debe generarse, almacenarse y usarse en un módulo criptográfico de hardware certificado como mínimo con FIPS 140-2 nivel 2 o Common Criteria EAL 4+ (§6.2.7.4.1). Las opciones permitidas son un módulo de hardware que operas tú, un servicio de claves en la nube cuyo HSM cumpla ese nivel y cuya suscripción esté «configurada para registrar todos los accesos, las operaciones y los cambios de configuración», o un servicio de firma. La CA tiene que verificarlo, por ejemplo, enviando un token con una clave generada de antemano o comprobando una atestación de la clave (§6.2.7.4.2). Antes de 2023, esto solo se aplicaba a los certificados EV.
  • Tamaños de clave. Claves RSA de al menos 3072 bits, o ECDSA sobre P-256, P-384 o P-521 (§6.1.5.2).
  • Vida útil. Como máximo 460 días para los certificados emitidos a partir del 1 de marzo de 2026; antes de esa fecha, 39 meses (§6.3.2).
  • Autoridades de sellado de tiempo. Los certificados de las TSA duran como máximo 135 meses y sus claves pueden usarse durante 15 meses a partir de notBefore; los tokens de sello de tiempo con SHA-1 están prohibidos desde abril de 2022.

Microsoft añade sus propias capas por encima. Desde Windows 10 versión 1607, los controladores en modo kernel deben estar firmados por Microsoft a través del Windows Hardware Dev Center, así que un certificado comercial por sí solo no basta para cargar un controlador (política de firma de controladores). Y EV ya no significa «sin advertencia de SmartScreen»: la página de Microsoft sobre el estado de las funciones de distribución dice que, desde el cambio del Trusted Root Program de 2024, «ningún tipo de certificado (OV o EV) concede una exención inmediata»; la reputación se acumula por hash de archivo y por identidad de firma.

El servicio gestionado de Microsoft, Artifact Signing (antes llamado Trusted Signing), guarda las claves en HSM operados por Microsoft y emite certificados que «se renuevan a diario y solo son válidos durante 72 horas» (gestión de certificados). Solo el resumen sale de tu equipo de compilación. Con certificados de vida tan corta, una firma sin sello de tiempo ya no sirve de nada tres días después. Dos detalles de la misma página: como el certificado cambia cada día, cada perfil de certificado recibe un EKU personalizado y estable bajo 1.3.6.1.4.1.311.97. al que anclar la confianza (por ejemplo, en las políticas de control de aplicaciones), y sus CA se están retirando de la CCADB y ya no forman parte del Microsoft Trusted Root Program, pero «siguen en la Windows Platform Trust List», de modo que Windows sigue confiando en ellas, mientras que otras plataformas quizá no.

Firma y verificación con osslsigncode

osslsigncode implementa Authenticode en Linux y macOS. En el laboratorio (paquete de Debian 2.14 en debian:forky-20261005-slim) creamos una jerarquía de prueba privada —una raíz, una CA emisora y un certificado de firma de código RSA 3072 con el EKU codeSigning—, firmamos el programa y pedimos un sello de tiempo a la TSA pública de DigiCert:

lab.sh (extracto): firmar con un sello de tiempo RFC 3161 y verificar
osslsigncode sign -certs chain.pem -key codesign.key -h sha256 -n "Hello" -i "https://example.test/hello" \
  -ts http://timestamp.digicert.com -in hello.exe -out hello-signed.exe
osslsigncode verify -CAfile root.pem -TSA-CAfile /etc/ssl/certs/ca-certificates.crt -in hello-signed.exe

A la TSA solo se envía un hash de la firma. Esta es la verificación, con los listados repetidos de la cadena abreviados:

osslsigncode verify (abreviado)
Message digest algorithm  : SHA256
Current message digest    : ADEAAA5366F711E064A9D54630D1E45520727730ECA3B85423D7570B0D41F7A7 
Calculated message digest : ADEAAA5366F711E064A9D54630D1E45520727730ECA3B85423D7570B0D41F7A7 

Signer's certificate:
	------------------
	Signer #0:
		Subject: CN=Example Software GmbH,O=Example Software GmbH,C=DE
		Issuer : CN=Example Test Code Signing CA,O=Example Test
		Serial : 20ADC33D7219F573885B44765077E96312F72F6D
		Certificate expiration date:
			notBefore : Oct 22 06:32:28 2026 GMT
			notAfter : Nov 21 06:32:28 2026 GMT

Authenticated attributes:
	Signing time: Oct 22 06:32:28 2026 GMT
	Microsoft Individual Code Signing purpose
	Message digest: DE669CE86A064FAEB4DFE8E43E14090CE5912FA036C527C13D5558B0BF110BB7 
	URL description: https://example.test/hello
	Text description: Hello

Countersignatures:
	Timestamp time: Oct 22 06:32:29 2026 GMT
	Signing time: Oct 22 06:32:29 2026 GMT
	Hash Algorithm: sha256
	Issuer: CN=DigiCert Trusted G4 TimeStamping RSA4096 SHA256 2025 CA1,O=DigiCert\, Inc.,C=US
	Serial: 084FDC334F7E454EDBC30F8FF9921835
...
Timestamp Server Signature CRL verification: ok
Timestamp Server Signature verification: ok
Signature verification time: Oct 22 06:32:29 2026 GMT
...
Signature verification: ok

Number of verified signatures: 1
Succeeded

«Current message digest» es el hash guardado en la firma, y «Calculated», el que se recalcula a partir del archivo. El «Signing time» del propio firmante no es más que lo que indica el reloj del firmante; el «Timestamp time» lo firma la TSA y es el que usan los verificadores. A continuación, osslsigncode validó la firma con la hora del sello de tiempo, que es la razón de ser de toda esta construcción.

Por qué importa el sello de tiempo

Para comprobarlo, en el laboratorio firmamos el mismo programa una segunda vez sin -ts y después verificamos ambos archivos como si fuera 60 días más tarde, cuando el certificado de prueba de 30 días ya había caducado (osslsigncode verify -time):

Verificación a fecha de hoy + 60 días (líneas filtradas, ubicación del error de OpenSSL abreviada)
hello-signed.exe   Signature verification time: Oct 22 06:32:29 2026 GMT Signature verification: ok Succeeded
hello-nots.exe     ...:PKCS7_verify:certificate verify error:...:Verify error: certificate has expired Signature verification time: Dec 21 06:32:29 2026 GMT Error: certificate has expired ... Signature verification: failed Failed

Con sello de tiempo, la comprobación se hace con la hora de firma demostrada, así que la firma sigue siendo válida después de que caduque el certificado de firma (en esta prueba, el propio certificado del servicio de sellado de tiempo de DigiCert es válido hasta noviembre de 2037). Sin él, la verificación usa la hora actual y falla el día en que caduca el certificado, con el mismo error certificate has expired que en cualquier cliente TLS. Microsoft va más allá: si el sello de tiempo cae dentro de los períodos de validez tanto del certificado de firma como del certificado de la TSA, la firma es válida, y «sigue siéndolo mucho después de que caduquen el certificado de firma y el certificado de la TSA (a menos que se revoque alguno de los dos)».

La revocación también depende del momento. Los CSBR permiten a una CA fijar la revocationDate de un certificado de firma de código en la fecha en que se consideró no válido por primera vez —en caso de compromiso de la clave, «la fecha más temprana de sospecha de compromiso»—, y «el software debería seguir tratando como válidos los objetos que contienen un sello de tiempo anterior a la fecha de revocación» (§4.9.6). Por tanto, una clave filtrada invalida las firmas con un sello de tiempo posterior al compromiso, no todas las versiones que hayas publicado.

Qué rompe una firma y qué no

Dos modificaciones, verificadas con el mismo comando:

Un byte de código modificado (desplazamiento 4096) y después 64 bytes añadidos dentro de la tabla de certificados
### one byte of code changed
Signature verification: failed
Message digest algorithm  : SHA256
Current message digest    : ADEAAA5366F711E064A9D54630D1E45520727730ECA3B85423D7570B0D41F7A7 
Calculated message digest : E754D0A7C1AB0CA1716866C67046BA54D2C195CDCBA4A182D56DD5C57FCD37B6     MISMATCH!!!
Failed
### extra bytes inside the certificate table
appended 64 bytes inside the certificate table: size 9240 -> 9304
Warning: invalid PE checksum
Calculated message digest : ADEAAA5366F711E064A9D54630D1E45520727730ECA3B85423D7570B0D41F7A7 
Timestamp Server Signature verification: ok
Signature verification: ok
Succeeded

En el segundo archivo, la entrada de la tabla de certificados y la longitud de WIN_CERTIFICATE crecieron 64 bytes, y en ese espacio se colocaron datos. Como la tabla está excluida del hash, la firma sigue verificándose; solo es incorrecta la suma de comprobación PE, que también está excluida. Esa es la estructura que hay detrás de CVE-2013-3900: los instaladores que leen su configuración del final de su propio archivo podían recibir la carga útil de un atacante sin que la firma quedara invalidada. Microsoft lo corrigió en 2013 con un ajuste del registro que hay que activar expresamente, EnableCertPaddingCheck, y su republicación de 2022 dice que «no tiene previsto aplicar por defecto el comportamiento de verificación más estricto». CISA añadió el CVE a su catálogo Known Exploited Vulnerabilities en enero de 2022. La lección para los desarrolladores: nunca leas datos de la zona de la tabla de certificados de tu propio ejecutable y activa EnableCertPaddingCheck en los equipos que administras.

En Windows: signtool

SignTool, del Windows SDK, es la implementación de referencia. Las versiones actuales exigen indicar los algoritmos de resumen de forma explícita: «El comando sign de SignTool requiere la opción del algoritmo de resumen del archivo (/fd) y la opción del algoritmo de resumen del sello de tiempo (/td)» (SignTool). Con el certificado en el almacén de Windows y la clave en un token, los comandos habituales son:

signtool (de la documentación de Microsoft; no se ejecutó aquí)
signtool sign /n "Example Software GmbH" /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /d "Hello" MyApp.exe
signtool verify /pa /tw /v MyApp.exe
  • /n o /sha1 selecciona el certificado; la clave se queda en el token o en el HSM, detrás de su CSP o KSP (/csp y /kc los indican de forma explícita).
  • /tr consulta a un servidor RFC 3161; la opción más antigua /t usa el protocolo de sellado de tiempo heredado de Authenticode.
  • verify /pa usa la política de Authenticode predeterminada; si se omite esa opción, SignTool aplica la política de controladores y rechaza las firmas normales. /tw avisa cuando una firma no tiene sello de tiempo; esta opción debería estar en todo pipeline de publicación.
  • /dlib y /dmdf conectan una biblioteca que firma el resumen en remoto; Artifact Signing incluye una, y Jsign, de código abierto (Apache-2.0, versión 7.6), hace lo mismo desde cualquier sistema operativo con AWS KMS, Google Cloud KMS, Azure Key Vault, tokens PKCS #11 y YubiKeys.

Para verificar, Get-AuthenticodeSignature de PowerShell devuelve el estado y el firmante, y la pestaña «Firmas digitales» del Explorador de Windows muestra el sello de tiempo.

Firma en un pipeline de compilación

Con claves ligadas al hardware, el servidor de compilación ya no guarda un archivo de clave. Hay tres esquemas que funcionan:

  1. Un servicio de claves en la nube: genera la clave en AWS KMS, Google Cloud HSM o Azure Key Vault Premium, obtén el certificado de una CA que acepte la atestación de claves o el informe de auditoría de ese servicio y firma el resumen en remoto con Jsign o con una herramienta del proveedor. El requisito del registro de auditoría forma parte de los CSBR; el artículo sobre la firma con KMS en la nube muestra qué registros están desactivados por defecto.
  2. Un servicio de firma gestionado, como Artifact Signing o el de una CA: el servicio guarda la clave y el certificado, y tú envías resúmenes.
  3. Un token en un equipo de firma dedicado, accesible solo desde la tarea de publicación. Es sencillo, pero ahora una sola máquina está en tu ruta crítica.

En todos los casos, firma solo los artefactos de publicación, desde una rama protegida y con sello de tiempo, y anota el hash del archivo y la identidad de firma en las notas de la versión para que los usuarios y las herramientas EDR puedan comprobar qué publicas.

Errores frecuentes

  • Sin sello de tiempo. Todo funciona hasta que caduca el certificado; a partir de ese día, cada copia instalada muestra una firma no válida. Con certificados de 72 horas o de 460 días, ese día llega pronto.
  • SHA-1 en cualquier parte. Antes, omitir /fd suponía usar SHA-1 por defecto; el SignTool actual lo rechaza, pero los scripts antiguos copiados de foros siguen conteniendo /fd sha1 o /t.
  • Política de verificación incorrecta. signtool verify sin /pa presenta una firma comercial válida como no fiable.
  • Un certificado sin el EKU codeSigning, o un certificado TLS reutilizado para firmar. Los verificadores lo rechazan, igual que OpenSSL informa de unsuitable certificate purpose.
  • Falta el certificado intermedio. Incluye la cadena completa en la firma (-certs chain.pem, o el certificado intermedio de la CA instalado en el equipo de firma); si no, la verificación depende de lo que tenga por casualidad el equipo del usuario.
  • Esperar que EV silencie SmartScreen. Ahora la reputación se gana con el tiempo; firma cada versión con la misma identidad para que se vaya acumulando.
  • Leer datos del final de tu propio archivo firmado (instaladores, autoextraíbles): esa zona no está cubierta por la firma.

Fuentes