💡 Exemple complet fonctionnel disponible sur GitHub :
digital-signing-certificate-validity-dotnet
Le problème de conformité que personne ne voit jusqu’à ce qu’un auditeur le signale
Un service de signature fonctionne depuis trois ans sans erreur. Les documents sont envoyés, les destinataires les acceptent, rien dans les journaux ne laisse penser à un problème. Puis le validateur d’une contre‑partie signale un lot comme invalide, et l’enquête révèle deux causes : les signatures ont été créées avec SHA‑1 et, depuis quatre mois, le certificat était expiré.
Les deux échecs étaient silencieux au moment de la signature. C’est ce que change GroupDocs.Signature 26.9.
L’application stricte de la validité du certificat devient le nouveau comportement par défaut pour la signature numérique .NET : un certificat hors de sa période de validité est rejeté plutôt qu’utilisé. Il arrive avec deux compagnons — SHA‑256 comme algorithme de hachage PDF par défaut, et un LogLevel qui filtre enfin — et, ensemble, ils déplacent trois catégories d’échecs du destinataire vers l’expéditeur, où ils peuvent encore être corrigés.
Pourquoi le succès silencieux est le résultat coûteux
La signature est particulière du fait que la partie qui commet l’erreur n’est pas celle qui la découvre. Une facture mal formée échoue dans votre propre système ; une signature invalide échoue dans celui de quelqu’un d’autre, des semaines plus tard, sans aucun diagnostic que vous puissiez lire.
Cette asymétrie explique pourquoi « l’API a renvoyé le succès » n’est pas une garantie utile ici. Les anciens paramètres par défaut étaient optimisés pour ne pas interrompre l’appelant, et le coût retombait sur le destinataire et, finalement, sur celui qui devait re‑signer et renvoyer plusieurs centaines de documents.
Changement 1 : Les certificats expirés sont rejetés
Le changement principal. Sign lève maintenant une GroupDocsSignatureException lorsque la période de validité du certificat est terminée ou n’a pas encore commencé, et rien n’est écrit sur le disque.
try
{
signature.Sign(outputPath, options);
return true;
}
catch (GroupDocsSignatureException ex)
{
Console.WriteLine($" Rejected: {ex.Message}");
return false;
}
Le message indique le certificat et la propriété qui le rendrait acceptable, de sorte qu’un opérateur lisant une ligne de journal puisse agir sans ouvrir la documentation. Pour un pipeline qui passe à la version 26.9 et commence à échouer, c’est presque toujours la raison — et la réponse correcte est le renouvellement, pas la suppression.
Lorsque vous avez réellement besoin de l’ancien comportement, il suffit d’une propriété :
var options = new DigitalSignOptions(certificate)
{
Password = certificatePassword,
AllowExpired = true
};
Le document est signé et un avertissement est envoyé au logger. Les validateurs rejettent toujours le résultat, car AllowExpired contrôle ce que la bibliothèque autorise, et non la valeur du certificat. Le drapeau compagnon AllowNotYetValid couvre l’autre extrémité de la fenêtre et est délibérément indépendant : autoriser un certificat expiré ne permet pas silencieusement d’en accepter un daté dans le futur.
Changement 2 : SHA‑256 par défaut
Les signatures numériques PDF sont désormais écrites avec SHA‑256 dans le format adbe.pkcs7.detached que les validateurs actuels attendent. Les versions antérieures utilisaient SHA‑1.
var options = new DigitalSignOptions(certificate)
{
Password = certificatePassword,
HashAlgorithm = HashAlgorithm.Sha256,
Reason = "Approved",
Location = "Head office"
};
Spécifier explicitement la propriété n’est nécessaire que pour aller plus loin — Sha384 ou Sha512 lorsqu’une politique l’exige — ou pour rester sur Sha1 avec un validateur qui ne peut rien d’autre gérer. Un horodatage ajouté à la signature utilise le même algorithme de hachage.
La vérification a changé dans la même version et dans la même direction : DigitalVerifyOptions sans critère était presque un no‑op, et effectue maintenant une vérification cryptographique complète, de sorte qu’un document modifié après la signature est signalé comme invalide.
Changement 3 : LogLevel filtre réellement
SignatureSettings accepte un logger depuis longtemps. Avant la version 26.9, le niveau était ignoré, de sorte que chaque message arrivait quel que soit le niveau, et la plupart des services désactivaient la journalisation plutôt que d’être submergés de traces.
L’exemple rend la différence mesurable en signant le même document trois fois avec un logger comptant :
var levels = new Dictionary<string, LogLevel>
{
["None"] = LogLevel.None,
["Warning | Error"] = LogLevel.Warning | LogLevel.Error,
["All"] = LogLevel.All
};
None ne produit aucun message, Warning | Error conserve le seul avertissement déclenché par le certificat expiré autorisé, et All ajoute une trace à chaque étape. Le logger comptant est le point d’intégration pour votre propre pile :
public void Warning(string message)
{
Warnings++;
WarningMessages.Add(message);
}
Implémentez ces trois méthodes avec Serilog, NLog ou Application Insights et les diagnostics de la bibliothèque atterriront où le reste de vos journaux de service se trouve.
Le niveau de journalisation change‑t‑il les exceptions que je reçois ?
Non, et il est utile d’être explicite parce que les deux semblent liés. LogLevel filtre ce qui atteint l’ILogger. Les exceptions sont levées vers votre code quoi qu’il arrive : un certificat expiré sans AllowExpired lève toujours une exception même avec LogLevel.None, et votre bloc catch se comporte de la même façon. Diagnostics et flux de contrôle sont des canaux séparés, ce qui rend sûr l’exécution en production avec Warning | Error.
Le rejet est moins coûteux qu’il n’y paraît
L’objection à un arrêt brutal est opérationnelle : un lot nocturne qui se terminait auparavant échoue maintenant à 02 h00 et quelqu’un est paginé.
C’est un coût réel, mais c’est encore le plus petit. Un lot rejeté représente une alerte, un renouvellement et un nouveau lancement, le tout à l’intérieur de vos propres systèmes. Un lot signé avec un certificat expiré est découvert par le destinataire, ce qui engendre un fil de support, une réémission de chaque document concerné, et une conversation délicate sur la durée du problème.
L’exemple rend l’échec concret plutôt que théorique : il signe volontairement avec un certificat expiré, intercepte l’exception et affiche le message, afin que vous puissiez voir exactement ce que vos journaux contiendront avant que la mise à jour n’atteigne la production. Je vous conseille d’exécuter cette méthode avec votre propre magasin de certificats avant de planifier le passage de version.
Que faire avant de mettre à jour
Trois vérifications, dans l’ordre de probabilité d’incident.
Examinez les dates d’expiration des certificats sur chaque chemin de signature, y compris ceux qui s’exécutent mensuellement ou trimestriellement — c’est là que les certificats expirés restent le plus longtemps cachés. Puis recherchez HashAlgorithm : si rien ne le définit, vos hachages passeront de SHA‑1 à SHA‑256 lors de la mise à jour, ce qui constitue une amélioration à mentionner dans les notes de version. Enfin, choisissez délibérément un niveau de journalisation. Le paramètre par défaut honnête pour un service est Warning | Error ; All sert à reproduire un problème précis, et None signifie renoncer au seul signal qui indique qu’une signature a été faite sous une dérogation.
La vérification a changé dans la même direction
Il est facile de passer à côté, car aucun changement de code appelant n’est requis. DigitalVerifyOptions sans critère était proche d’un no‑op : il comparait les critères fournis, et en l’absence de critères, il n’avait guère quoi dire. Depuis la version 26.9, le même appel effectue une vérification cryptographique complète de chaque signature numérique PDF.
Pour un service qui vérifie les documents entrants, cela représente une mise à jour silencieuse de « il y a une signature ici » à « cette signature correspond à ce contenu ». C’est bon à savoir avant de voir un document échouer à la vérification alors qu’il passait le mois précédent : le document a probablement été altéré, et la vérification plus ancienne ne le détectait tout simplement pas.
Les certificats dans l’exemple
Un détail à copier plutôt que le code : l’exemple ne fournit aucune clé privée. TestCertificates.cs génère trois PFX auto‑signés en mémoire au moment de l’exécution — valide, expiré l’an dernier, valide à partir de l’année prochaine — de sorte que la démonstration fonctionne quelle que soit la date du jour et qu’il n’y ait rien de sensible dans le dépôt.
Ce modèle vaut la peine d’être adopté dans vos propres suites de tests. Un certificat de test engagé finit par expirer, et lorsqu’il le fait, l’échec ressemble exactement au bug que cette version a été conçue pour mettre en évidence.
Conclusion
Trois changements, une direction : les échecs qui apparaissaient auparavant chez le destinataire apparaissent maintenant chez l’expéditeur. Renouvelez le certificat plutôt que d’utiliser AllowExpired, laissez SHA‑256 être le défaut, vérifiez cryptographiquement les documents entrants, et choisissez un niveau de journalisation avant d’en avoir besoin. L’exemple exécute les six comportements en un seul passage, y compris le rejet, afin que la mise à jour puisse être répétée en quelques minutes.