💡 Volledig werkend voorbeeld beschikbaar op GitHub:
digital-signing-certificate-validity-dotnet

Het compliance‑probleem dat niemand ziet totdat een auditor het opmerkt

Een ondertekeningsservice draait drie jaar zonder fout. Documenten worden verzonden, ontvangers accepteren ze, en er staat niets in de logboeken dat op een probleem wijst. Vervolgens markeert de validator van een tegenpartij een batch als ongeldig, en het onderzoek brengt twee oorzaken aan het licht: de handtekeningen waren geschreven met SHA‑1, en gedurende de laatste vier maanden was het certificaat verlopen.

Beide fouten waren stil op het moment van ondertekenen. Dat is wat GroupDocs.Signature 26.9 verandert.

Handhaving van certificaatgeldigheid is het nieuwe standaardgedrag voor .NET‑digitale ondertekening: een certificaat buiten zijn geldigheidsperiode wordt afgewezen in plaats van gebruikt. Het wordt geleverd met twee metgezellen – SHA‑256 als standaard‑PDF‑digest, en een LogLevel dat eindelijk filtert – en samen verplaatsen ze drie klassen van fouten van de ontvanger terug naar de afzender, waar ze nog steeds kunnen worden opgelost.

Waarom stilzwijgende succes de dure uitkomst is

Ondertekenen is ongebruikelijk omdat de partij die de fout maakt niet dezelfde is die hem ontdekt. Een verkeerd opgemaakte factuur faalt in je eigen systeem; een ongeldige handtekening faalt weken later in het systeem van iemand anders, zonder diagnostiek die je kunt lezen.

Die asymmetrie is de reden waarom “de API gaf succes terug” hier geen nuttige garantie is. De oude standaardinstellingen waren geoptimaliseerd om de aanroeper niet te onderbreken, en de kosten belandden bij de ontvanger en uiteindelijk bij degene die honderden documenten opnieuw moest ondertekenen en opnieuw verzenden.

Wijziging 1: Verlopen certificaten worden afgewezen

De headline‑wijziging. Sign gooit nu een GroupDocsSignatureException wanneer de geldigheid van het certificaat is verlopen of nog niet is begonnen, en er wordt niets naar schijf geschreven.

try
{
    signature.Sign(outputPath, options);
    return true;
}
catch (GroupDocsSignatureException ex)
{
    Console.WriteLine($"   Rejected: {ex.Message}");
    return false;
}

Het bericht noemt het certificaat en de eigenschap die het zou toestaan, zodat een operator die een logregel leest direct kan handelen zonder de documentatie te openen. Voor een pipeline die upgrade naar 26.9 uitvoert en begint te falen, is dit bijna altijd de reden – en de juiste reactie is verlenging, niet onderdrukking.

Wanneer je echt het oude gedrag nodig hebt, is er één eigenschap:

var options = new DigitalSignOptions(certificate)
{
    Password = certificatePassword,
    AllowExpired = true
};

Het document wordt ondertekend en er gaat een waarschuwing naar de logger. Validators blijven het resultaat afwijzen, omdat AllowExpired bepaalt wat de bibliotheek toestaat en niet wat het certificaat waard is. De bijbehorende vlag AllowNotYetValid dekt het andere uiteinde van het venster en is bewust onafhankelijk: het toestaan van een verlopen certificaat betekent niet stilzwijgend het toestaan van een toekomstig gedateerd certificaat.

Wijziging 2: SHA‑256 als standaard

PDF‑digitale handtekeningen worden nu geschreven met SHA‑256 in het adbe.pkcs7.detached‑formaat dat huidige validators verwachten. Eerdere versies schreven SHA‑1.

var options = new DigitalSignOptions(certificate)
{
    Password = certificatePassword,
    HashAlgorithm = HashAlgorithm.Sha256,
    Reason = "Approved",
    Location = "Head office"
};

De eigenschap expliciet instellen is alleen nodig om verder te gaan – Sha384 of Sha512 wanneer een beleid dat vereist – of om bij Sha1 te blijven voor een validator die niets anders aankan. Een tijdstempel die aan de handtekening wordt toegevoegd, gebruikt dezelfde digest.

Verificatie veranderde in dezelfde release en in dezelfde richting: DigitalVerifyOptions zonder criteria was bijna een no‑op, en voert nu een volledige cryptografische controle uit, zodat een document dat na ondertekening is gewijzigd als ongeldig wordt gerapporteerd.

Wijziging 3: LogLevel filtert daadwerkelijk

SignatureSettings accepteert al lange tijd een logger. Voor 26.9 werd het niveau genegeerd, zodat elk bericht binnenkwam en de meeste services logging uitschakelden in plaats van te verdrinken in traces.

Het voorbeeld maakt het verschil meetbaar door hetzelfde document drie keer te ondertekenen met een tel‑logger:

var levels = new Dictionary<string, LogLevel>
{
    ["None"] = LogLevel.None,
    ["Warning | Error"] = LogLevel.Warning | LogLevel.Error,
    ["All"] = LogLevel.All
};

None produceert nul berichten, Warning | Error houdt de enkele waarschuwing die wordt opgegeven door het toegestane verlopen certificaat, en All voegt een trace per stap toe. De tel‑logger zelf is het integratiepunt voor je eigen stack:

public void Warning(string message)
{
    Warnings++;
    WarningMessages.Add(message);
}

Implementeer die drie methoden met Serilog, NLog of Application Insights en de diagnostiek van de bibliotheek belandt waar de rest van je service‑logboeken terechtkomt.

Verandert het logniveau welke uitzonderingen ik krijg?

Nee, en het is de moeite waard dit expliciet te maken omdat de twee gerelateerd lijken. LogLevel filtert wat de ILogger bereikt. Uitzonderingen worden ongeacht van je code gegooid: een verlopen certificaat zonder AllowExpired gooit nog steeds bij LogLevel.None, en je catch‑blok gedraagt zich identiek. Diagnostiek en controle‑stroom zijn gescheiden kanalen, en dat maakt het veilig om productie te draaien op Warning | Error.

De afwijzing is goedkoper dan hij lijkt

De bezwaar tegen een harde stop is operationeel: een nachtelijke batch die vroeger voltooid werd, faalt nu om 02:00 en iemand wordt gepaged. Dat is een reële kost, en het blijft de kleinere. Een afgewezen batch is één alarm, één verlenging en één herrun, allemaal binnen je eigen systemen. Een batch ondertekend met een verlopen certificaat wordt ontdekt door een ontvanger, wat betekent een support‑thread, een heruitgave van elk getroffen document, en een ongemakkelijke conversatie over hoe lang dit al gebeurde.

Het voorbeeld maakt de fout concreet in plaats van theoretisch: het ondertekent opzettelijk met een verlopen certificaat, vangt de uitzondering op, en print het bericht, zodat je precies ziet wat je logboeken zullen bevatten voordat de upgrade in productie gaat. Ik zou die ene methode tegen je eigen certificaatopslag draaien voordat je de versie‑sprong plant.

Wat te doen vóór het upgraden

Drie controles, in volgorde van waarschijnlijkheid dat ze je bijten.

Bekijk de vervaldatum van certificaten over elk ondertekeningspad, inclusief die die maandelijks of per kwartaal draaien – daar blijft een verlopen certificaat het langst verborgen. Zoek vervolgens naar HashAlgorithm: als niets het instelt, veranderen je digests bij de upgrade van SHA‑1 naar SHA‑256, wat een verbetering is die nog in de release‑notes moet staan. Bepaal tenslotte bewust een logniveau. De eerlijke standaard voor een service is Warning | Error; All is voor het reproduceren van een specifiek probleem, en None betekent dat je het enige signaal opgeeft dat aangeeft dat een handtekening onder een vrijstelling is gemaakt.

Verificatie veranderde in dezelfde richting

Het is makkelijk te missen, omdat er niets in de aanroepende code hoeft te veranderen. DigitalVerifyOptions zonder criteria was bijna een no‑op: het vergeleek de criteria die het kreeg, en zonder criteria had het weinig te zeggen. Vanaf 26.9 voert dezelfde oproep een volledige cryptografische controle uit van elke PDF‑digitale handtekening.

Voor een service die binnenkomende documenten verifieert, is dat een stille upgrade van “er is hier een handtekening” naar “deze handtekening komt overeen met deze inhoud”. Handig om te weten voordat je een document ziet falen bij verificatie die vorige maand nog slaagde: het document is waarschijnlijk gewijzigd, en de oudere controle keek er simpelweg niet naar.

De certificaten in het voorbeeld

Een detail dat je beter kunt kopiëren dan de code: het voorbeeld bevat geen privésleutel. TestCertificates.cs bouwt drie zelf‑ondertekende PFX‑bestanden in het geheugen tijdens runtime – geldig, vorig jaar verlopen, en geldig vanaf volgend jaar – zodat de demonstratie werkt ongeacht de huidige datum en er niets gevoelig in de repository staat.

Dat patroon is het waard om in je eigen testsuites over te nemen. Een gecommitteerd testcertificaat verloopt uiteindelijk, en wanneer dat gebeurt, ziet de fout er precies uit als de bug die deze release aan het licht wilde brengen.

Conclusie

Drie wijzigingen, één richting: fouten die vroeger bij de ontvanger verschenen, verschijnen nu bij de afzender. Verleng het certificaat in plaats van AllowExpired te gebruiken, laat SHA‑256 de standaard zijn, verifieer binnenkomende documenten cryptografisch, en kies een logniveau voordat je het nodig hebt. Het voorbeeld doorloopt alle zes gedragingen in één run, inclusief de afwijzing, zodat de upgrade in een paar minuten kan worden geoefend.

Aanvullende bronnen