Aller au contenu
ecdsa.com
← Tous les articles

Signature Authenticode : clés, horodatages, vérification

Nous avons signé un petit exécutable Windows avec osslsigncode 2.14 et un horodatage DigiCert, puis nous l’avons soumis à trois tests : une copie signée sans horodatage, un octet de code modifié et des données cachées dans la table des certificats. Les règles sont tirées des Code Signing Baseline Requirements 3.11.0 du CA/Browser Forum et de la documentation de Microsoft, vérifiées en octobre 2026.

En bref. Authenticode intègre un SignedData PKCS #7 dans une table située à la fin d’un fichier PE Windows ; la signature couvre un condensat du fichier qui exclut la somme de contrôle, le pointeur vers cette table et la table elle-même. Depuis le 1er juin 2023, la clé d’un certificat de signature de code de confiance publique doit être générée et utilisée dans un module matériel (un jeton matériel, un HSM sur site ou un service HSM dans le cloud avec journalisation d’audit complète) : le traditionnel fichier .pfx posé sur un serveur de build ne permet donc plus d’obtenir un certificat. Les certificats émis depuis le 1er mars 2026 ont une durée de vie de 460 jours au plus, et ceux qu’émet Artifact Signing, le service de Microsoft, de 72 heures, ce qui fait de l’horodatage RFC 3161 l’élément qui maintient une signature valide. Signez toujours avec /fd SHA256 /tr <TSA URL> /td SHA256. Les certificats EV n’apportent plus de réputation SmartScreen immédiate. Vérifiez avec signtool verify /pa /tw sous Windows, ou avec osslsigncode verify ailleurs.

Ce qui est signé

Un fichier PE (.exe, .dll, .sys) comporte un répertoire de données dans son en-tête facultatif (optional header) ; l’entrée 4, la table des certificats (Certificate Table), contient l’offset dans le fichier et la taille d’une liste de structures WIN_CERTIFICATE ajoutées à la fin du fichier. Pour Authenticode, l’entrée a la révision 0x0200 et le type WIN_CERT_TYPE_PKCS_SIGNED_DATA (0x0002), et contient un SignedData PKCS #7 (format PE).

Le contenu signé n’est pas le fichier, mais une structure SpcIndirectDataContent qui contient le condensat du fichier. Ce condensat ne peut pas couvrir la signature elle-même : selon les termes de Microsoft, il « doit exclure certains champs de l’image PE, comme la somme de contrôle et l’entrée Certificate Table des répertoires de données de l’en-tête facultatif », ainsi que la table des certificats. Tout le reste est haché : en-têtes, sections et données situées après la dernière section.

Notre échantillon est un programme de 1,6 Mo obtenu par compilation croisée avec Go 1.27.3 (GOOS=windows GOARCH=amd64). Un court script Python lit l’entrée de la table des certificats avant et après la signature :

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

Ces 9240 octets contiennent la signature, la chaîne de certificats et le jeton d’horodatage. La liste des identifiants d’objet présents dans la signature extraite montre les éléments propres à Authenticode à côté des éléments CMS habituels :

openssl asn1parse -inform DER -in sig.der | grep -o 'OBJECT *:[^ ]*' | sort | uniq -c (extrait)
      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 désigne SpcIndirectDataContent, …2.1.15 les données de l’image PE qu’il contient, …2.1.11 et …2.1.12 le type de déclaration (statement type) ainsi que le nom et l’URL du programme, et 1.3.6.1.4.1.311.3.3.1 le jeton d’horodatage RFC 3161, stocké comme attribut non signé et dont le type de contenu est id-smime-ct-TSTInfo.

Certificats et clés en 2026

Les certificats de signature de code de confiance publique sont régis par les Code Signing Baseline Requirements (CSBR) du CA/Browser Forum, version 3.11.0 du 16 juin 2026. Voici les règles qui changent votre façon de produire vos builds :

  • Des clés matérielles pour tous. « À compter du 1er juin 2023 », la clé privée de l’abonné doit être générée, stockée et utilisée dans un module cryptographique matériel certifié au minimum FIPS 140-2 niveau 2 ou Critères communs EAL 4+ (§6.2.7.4.1). Les options admises sont un module matériel que vous exploitez vous-même, un service de clés dans le cloud dont le HSM atteint ce niveau et dont l’abonnement est « configuré pour journaliser tous les accès, toutes les opérations et toutes les modifications de configuration », ou un service de signature. L’AC doit s’en assurer, par exemple en livrant un jeton matériel avec une clé pré-générée ou en contrôlant une attestation de clé (§6.2.7.4.2). Avant 2023, cette règle ne concernait que les certificats EV.
  • Tailles de clé. Clés RSA d’au moins 3072 bits, ou ECDSA sur P-256, P-384 ou P-521 (§6.1.5.2).
  • Durée de vie. 460 jours au plus pour les certificats émis à partir du 1er mars 2026 ; 39 mois auparavant (§6.3.2).
  • Autorités d’horodatage. Les certificats de TSA durent au plus 135 mois et leurs clés peuvent servir pendant 15 mois après notBefore ; les jetons d’horodatage SHA-1 sont interdits depuis avril 2022.

Microsoft y ajoute ses propres exigences. Depuis Windows 10 version 1607, les pilotes en mode noyau doivent être signés par Microsoft via le Windows Hardware Dev Center : un certificat commercial seul ne suffit donc pas à faire charger un pilote (politique de signature des pilotes). Et EV ne signifie plus « aucun avertissement SmartScreen » : la page état des fonctionnalités de distribution de Microsoft indique que, depuis la modification du Trusted Root Program en 2024, « aucun type de certificat (OV ou EV) n’accorde de contournement immédiat » ; la réputation s’accumule par condensat de fichier et par identité de signataire.

Le service géré de Microsoft, Artifact Signing (anciennement Trusted Signing), conserve les clés dans des HSM exploités par Microsoft et émet des certificats qui « sont renouvelés chaque jour et ne sont valides que 72 heures » (gestion des certificats). Seul le condensat quitte votre machine de build. Avec des certificats aussi courts, une signature sans horodatage ne vaut plus rien trois jours plus tard. Deux détails de la même page : comme le certificat change chaque jour, chaque profil de certificat reçoit une EKU personnalisée et durable sous 1.3.6.1.4.1.311.97., sur laquelle épingler la confiance (par exemple dans les politiques de contrôle des applications) ; et les AC du service sont en cours de retrait de la CCADB et ne font plus partie du Microsoft Trusted Root Program, mais « restent sur la Windows Platform Trust List » : Windows continue donc de leur faire confiance, ce que d’autres plateformes peuvent ne pas faire.

Signer et vérifier avec osslsigncode

osslsigncode implémente Authenticode sous Linux et macOS. Pour nos essais (paquet Debian 2.14 dans debian:forky-20261005-slim), nous avons créé une hiérarchie de test privée (une racine, une AC émettrice et un certificat de signature de code RSA 3072 avec l’EKU codeSigning), signé le programme et demandé un horodatage à la TSA publique de DigiCert :

lab.sh (extrait) : signer avec un horodatage RFC 3161, vérifier
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

Seul un condensat de la signature est envoyé à la TSA. Voici la vérification, avec les listes répétées de la chaîne raccourcies :

osslsigncode verify (abrégé)
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 » est le condensat stocké dans la signature, « Calculated » celui qui est recalculé à partir du fichier. Le « Signing time » du signataire n’est que ce qu’affirme son horloge ; le « Timestamp time » est signé par la TSA, et c’est sur lui que s’appuient les vérificateurs. osslsigncode a ensuite vérifié la signature à l’heure de l’horodatage, ce qui est tout l’intérêt de la construction.

Pourquoi l’horodatage est essentiel

Pour le constater, nous avons signé le même programme une seconde fois sans -ts, puis vérifié les deux fichiers comme si nous étions 60 jours plus tard, une fois expiré le certificat de test de 30 jours (osslsigncode verify -time) :

Vérification à la date du jour + 60 jours (lignes filtrées, emplacement de l’erreur OpenSSL raccourci)
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

Avec un horodatage, la vérification se fait à l’heure de signature prouvée, si bien que la signature survit au certificat de signature (dans cet essai, le certificat du répondeur d’horodatage de DigiCert est lui-même valide jusqu’en novembre 2037). Sans horodatage, la vérification utilise l’heure actuelle et échoue le jour où le certificat expire, avec la même erreur certificate has expired que n’importe quel client TLS. Microsoft va plus loin : si l’horodatage tombe dans les périodes de validité du certificat de signature et du certificat de la TSA, la signature est valide, et « elle reste valide bien après l’expiration du certificat de signature et du certificat de la TSA (sauf si l’un d’eux est révoqué) ».

La révocation dépend elle aussi du temps. Les CSBR permettent à une AC de fixer la revocationDate d’un certificat de signature de code à la date à laquelle il a été considéré pour la première fois comme invalide, soit, en cas de compromission de clé, « la date la plus ancienne de compromission suspectée », et « les logiciels devraient continuer à traiter comme valides les objets contenant un horodatage antérieur à la date de révocation » (§4.9.6). Une clé divulguée invalide donc les signatures horodatées après la compromission, et non toutes les versions que vous avez publiées.

Ce qui casse une signature, et ce qui ne la casse pas

Deux modifications, vérifiées avec la même commande :

Un octet de code modifié (offset 4096), puis 64 octets ajoutés dans la table des certificats
### 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

Dans le second fichier, l’entrée de la table des certificats et la longueur du WIN_CERTIFICATE ont été augmentées de 64 octets, et des données ont été placées dans cet espace. Comme la table est exclue du condensat, la vérification de la signature réussit toujours ; seule la somme de contrôle PE, elle aussi exclue, est fausse. C’est la disposition à l’origine de la CVE-2013-3900 : des programmes d’installation qui lisaient leur configuration à la fin de leur propre fichier pouvaient recevoir la charge utile d’un attaquant sans que la signature soit invalidée. Microsoft l’a corrigée en 2013 sous la forme d’un paramètre de registre à activer explicitement, EnableCertPaddingCheck, et sa republication de 2022 indique que Microsoft « ne prévoit pas d’imposer par défaut le comportement de vérification plus strict ». La CISA a ajouté la CVE à son catalogue Known Exploited Vulnerabilities en janvier 2022. La leçon pour les développeurs : ne lisez jamais de données dans la zone de la table des certificats de votre propre exécutable, et activez EnableCertPaddingCheck sur les machines que vous gérez.

Sous Windows : signtool

SignTool, fourni avec le SDK Windows, est l’implémentation de référence. Les versions actuelles exigent que les algorithmes de condensat soient indiqués explicitement : « La commande sign de SignTool exige l’option d’algorithme de condensat du fichier (/fd) et l’option d’algorithme de condensat de l’horodatage (/td) » (SignTool). Avec le certificat dans le magasin de certificats Windows et la clé sur un jeton matériel, les commandes habituelles sont les suivantes :

signtool (d’après la documentation de Microsoft ; non exécuté ici)
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 ou /sha1 sélectionne le certificat ; la clé reste dans le jeton ou le HSM, derrière son CSP ou son KSP (/csp et /kc les désignent explicitement).
  • /tr interroge un serveur RFC 3161 ; l’option /t, plus ancienne, utilise le protocole d’horodatage Authenticode historique.
  • verify /pa utilise la politique Authenticode par défaut ; sans cette option, SignTool applique la politique des pilotes et rejette les signatures ordinaires. /tw avertit quand une signature n’a pas d’horodatage, une option qui a sa place dans tout pipeline de publication.
  • /dlib et /dmdf branchent une bibliothèque qui signe le condensat à distance ; Artifact Signing en fournit une, et Jsign, open source (Apache-2.0, version 7.6), fait de même depuis n’importe quel système d’exploitation avec AWS KMS, Google Cloud KMS, Azure Key Vault, des jetons PKCS #11 et des YubiKey.

Côté vérification, Get-AuthenticodeSignature de PowerShell renvoie l’état et le signataire, et l’onglet « Signatures numériques » de l’Explorateur Windows affiche l’horodatage.

Signer dans un pipeline de build

Avec des clés liées au matériel, le serveur de build ne détient plus de fichier de clé. Trois configurations fonctionnent :

  1. Un service de clés dans le cloud : générez la clé dans AWS KMS, Google Cloud HSM ou Azure Key Vault Premium, obtenez le certificat auprès d’une AC qui accepte l’attestation de clé ou le rapport d’audit de ce service, et signez le condensat à distance avec Jsign ou un outil du fournisseur. L’exigence d’un journal d’audit fait partie des CSBR ; signer avec un KMS dans le cloud montre quels journaux sont désactivés par défaut.
  2. Un service de signature géré, comme Artifact Signing ou celui d’une AC : le service détient la clé et le certificat, vous envoyez des condensats.
  3. Un jeton matériel sur un hôte de signature dédié, accessible uniquement depuis la tâche de publication. C’est simple, mais une seule machine se trouve désormais sur votre chemin critique.

Dans tous les cas, ne signez que les artefacts de version, depuis une branche protégée, avec l’horodatage, et consignez le condensat du fichier et l’identité de signataire dans les notes de version, afin que les utilisateurs et les outils EDR puissent contrôler ce que vous avez publié.

Erreurs fréquentes

  • Pas d’horodatage. Tout fonctionne jusqu’à l’expiration du certificat, puis chaque copie installée affiche une signature invalide. Avec des certificats de 72 heures ou de 460 jours, ce jour arrive vite.
  • Du SHA-1 où que ce soit. Omettre /fd revenait autrefois à utiliser SHA-1 par défaut ; le SignTool actuel refuse, mais d’anciens scripts copiés sur des forums contiennent encore /fd sha1 ou /t.
  • Mauvaise politique de vérification. signtool verify sans /pa signale une signature commerciale valide comme non approuvée.
  • Un certificat sans l’EKU codeSigning, ou un certificat TLS détourné de son usage. Les vérificateurs le rejettent, de même qu’OpenSSL signale unsuitable certificate purpose.
  • Certificat intermédiaire manquant. Incluez la chaîne complète dans la signature (-certs chain.pem, ou le certificat intermédiaire de l’AC installé sur l’hôte de signature), sinon la vérification dépend de ce que la machine de l’utilisateur se trouve contenir.
  • Compter sur EV pour faire taire SmartScreen. La réputation se construit désormais avec le temps ; signez chaque version avec la même identité pour que cette réputation se reporte d’une version à l’autre.
  • Lire des données à la fin de votre propre fichier signé (programmes d’installation, archives auto-extractibles) : cette zone n’est pas couverte par la signature.

Sources