💡 Voll funktionsfähiges Beispiel auf GitHub verfügbar:
digital-signing-certificate-validity-dotnet

Das Compliance-Problem, das niemand sieht, bis ein Prüfer es entdeckt

Ein Signaturdienst läuft drei Jahre lang fehlerfrei. Dokumente werden verschickt, Empfänger akzeptieren sie, und die Protokolle zeigen kein Problem. Dann markiert der Validator eines Gegenübers einen Stapel als ungültig, und die Untersuchung ergibt zwei Ursachen: Die Signaturen wurden mit SHA-1 erstellt und das Zertifikat war in den letzten vier Monaten abgelaufen.

Beide Fehler blieben beim Signieren still. Das ändert GroupDocs.Signature 26.9.

Die Durchsetzung der Zertifikatsgültigkeit ist das neue Standardverhalten für .NET-Digital Signaturen: Ein Zertifikat außerhalb seines Gültigkeitszeitraums wird abgelehnt statt verwendet. Es kommt mit zwei Begleitern – SHA-256 als Standard‑PDF‑Digest und einem LogLevel, das endlich filtert – und zusammen verlagern sie drei Fehlertypen vom Empfänger zurück zum Absender, wo sie noch behoben werden können.

Warum stiller Erfolg die teure Konsequenz ist

Signieren ist ungewöhnlich, weil die Partei, die den Fehler macht, nicht diejenige ist, die ihn entdeckt. Eine fehlerhafte Rechnung schlägt in Ihrem eigenen System fehl; eine ungültige Signatur schlägt erst Wochen später im System eines anderen fehl, ohne dass Sie eine diagnostizierbare Meldung erhalten.

Diese Asymmetrie erklärt, warum „die API hat Erfolg zurückgegeben“ hier keine nützliche Garantie ist. Die alten Vorgaben waren darauf optimiert, den Aufrufer nicht zu unterbrechen, und die Kosten landeten beim Empfänger und schließlich bei demjenigen, der mehrere hundert Dokumente erneut signieren und senden musste.

Änderung 1: Abgelaufene Zertifikate werden abgelehnt

Die zentrale Änderung. Sign wirft jetzt GroupDocsSignatureException, wenn die Gültigkeit des Zertifikats beendet ist oder noch nicht begonnen hat, und es wird nichts auf die Festplatte geschrieben.

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

Die Meldung nennt das Zertifikat und die Eigenschaft, die es erlauben würde, sodass ein Bediener, der eine Protokollzeile liest, handeln kann, ohne die Dokumentation zu öffnen. Für eine Pipeline, die auf 26.9 aktualisiert wird und Fehler erzeugt, ist dies fast immer der Grund – und die richtige Reaktion ist die Erneuerung, nicht das Unterdrücken.

Wenn Sie das alte Verhalten wirklich benötigen, gibt es dafür eine Eigenschaft:

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

Das Dokument wird signiert und eine Warnung an den Logger gesendet. Validatoren lehnen das Ergebnis weiterhin ab, weil AllowExpired regelt, was die Bibliothek erlaubt, und nicht, was das Zertifikat wert ist. Das Begleit‑Flag AllowNotYetValid deckt das andere Ende des Zeitfensters ab und ist bewusst unabhängig: Das Zulassen eines abgelaufenen Zertifikats erlaubt nicht stillschweigend ein zukünftiges.

Änderung 2: SHA-256 als Standard

PDF‑Digitalsignaturen werden jetzt mit SHA-256 im adbe.pkcs7.detached‑Format erstellt, das aktuelle Validatoren erwarten. Frühere Versionen verwendeten SHA-1.

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

Das explizite Setzen der Eigenschaft ist nur nötig, um weiterzugehen – Sha384 oder Sha512, wenn eine Richtlinie dies verlangt – oder um bei Sha1 zu bleiben, wenn ein Validator nichts anderes verarbeiten kann. Ein Zeitstempel, der der Signatur hinzugefügt wird, verwendet denselben Digest.

Die Verifizierung hat sich im selben Release und in dieselbe Richtung geändert: DigitalVerifyOptions ohne Kriterien war früher praktisch ein No‑Op, jetzt führt es eine vollständige kryptografische Prüfung durch, sodass ein nach dem Signieren verändertes Dokument als ungültig gemeldet wird.

Änderung 3: LogLevel filtert tatsächlich

SignatureSettings akzeptiert schon lange einen Logger. Vor 26.9 wurde das Level ignoriert, sodass jede Meldung ankam und die meisten Dienste das Logging ausschalteten, anstatt in Traces zu ersticken.

Das Beispiel macht den Unterschied messbar, indem es dasselbe Dokument dreimal mit einem zählenden Logger signiert:

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

None erzeugt keine Meldungen, Warning | Error behält die einzelne Warnung bei, die durch das erlaubte abgelaufene Zertifikat ausgelöst wird, und All fügt pro Schritt einen Trace hinzu. Der zählende Logger selbst ist der Integrationspunkt für Ihren eigenen Stack:

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

Implementieren Sie diese drei Methoden für Serilog, NLog oder Application Insights, und die Diagnosen der Bibliothek landen dort, wo die übrigen Protokolle Ihres Dienstes geschrieben werden.

Ändert das LogLevel, welche Ausnahmen ich erhalte?

Nein, und es ist wichtig, das ausdrücklich zu sagen, weil die beiden zusammenzuhängen scheinen. LogLevel filtert, was den ILogger erreicht. Ausnahmen werden unabhängig davon geworfen: Ein abgelaufenes Zertifikat ohne AllowExpired wirft weiterhin bei LogLevel.None, und Ihr Catch‑Block verhält sich identisch. Diagnosen und Kontrollfluss sind getrennte Kanäle, was es sicher macht, die Produktion bei Warning | Error zu betreiben.

Die Ablehnung ist günstiger, als es scheint

Der Einwand gegen einen harten Stopp ist betriebsbedingt: Ein nächtlicher Batch, der früher fertig wurde, schlägt jetzt um 02:00 Uhr fehl und jemand wird benachrichtigt. Das ist ein echter Aufwand, und er ist immer noch der kleinere. Ein abgelehnter Batch bedeutet eine Alarmmeldung, eine Erneuerung und einen erneuten Durchlauf, alles innerhalb Ihrer eigenen Systeme. Ein mit einem abgelaufenen Zertifikat signierter Batch wird vom Empfänger entdeckt, was einen Support‑Thread, die Neu‑Ausstellung jedes betroffenen Dokuments und ein unangenehmes Gespräch darüber bedeutet, wie lange das bereits geschah.

Das Beispiel macht den Fehler greifbar statt theoretisch: Es signiert absichtlich mit einem abgelaufenen Zertifikat, fängt die Ausnahme ab und gibt die Meldung aus, sodass Sie genau sehen können, was Ihre Protokolle enthalten, bevor das Upgrade in die Produktion geht. Ich würde diese Methode gegen Ihren eigenen Zertifikatspeicher ausführen, bevor Sie das Versionsupdate planen.

Was vor dem Upgrade zu tun ist

Drei Prüfungen, nach der Wahrscheinlichkeit ihres Auftretens.

Überprüfen Sie das Ablaufdatum der Zertifikate in allen Signaturpfaden, einschließlich derjenigen, die monatlich oder vierteljährlich laufen – dort versteckt sich ein abgelaufenes Zertifikat am längsten. Durchsuchen Sie dann nach HashAlgorithm: Wenn nichts es setzt, ändern sich Ihre Digests beim Upgrade von SHA-1 zu SHA-256, was eine Verbesserung ist, die dennoch in den Release‑Notes erwähnt werden sollte. Entscheiden Sie schließlich bewusst über ein LogLevel. Der ehrliche Standard für einen Dienst ist Warning | Error; All dient zur Reproduktion eines spezifischen Problems, und None bedeutet, das einzige Signal aufzugeben, das Ihnen sagt, dass eine Signatur unter einer Ausnahme erstellt wurde.

Verifizierung hat sich in dieselbe Richtung geändert

Es ist leicht zu übersehen, weil im Aufrufcode nichts geändert werden muss. DigitalVerifyOptions ohne gesetzte Kriterien war fast ein No‑Op: Es verglich die übergebenen Kriterien, und ohne welche hatte es wenig zu sagen. Ab 26.9 führt derselbe Aufruf eine vollständige kryptografische Prüfung jeder PDF‑Digitalsignatur durch.

Für einen Dienst, der eingehende Dokumente prüft, ist das ein stilles Upgrade von „hier gibt es eine Signatur“ zu „diese Signatur stimmt mit diesem Inhalt überein“. Es ist gut zu wissen, bevor Sie ein Dokument sehen, das die Verifizierung, die letzten Monat bestand, jetzt fehlschlägt: Das Dokument wurde wahrscheinlich verändert, und die ältere Prüfung hat das einfach nicht erkannt.

Die Zertifikate im Beispiel

Ein Detail, das man eher kopieren als den Code, ist: Das Beispiel enthält keinen privaten Schlüssel. TestCertificates.cs erzeugt zur Laufzeit im Speicher drei selbstsignierte PFXs – gültig, im letzten Jahr abgelaufen, ab dem nächsten Jahr gültig – sodass die Demonstration unabhängig vom heutigen Datum funktioniert und nichts Sensibles im Repository liegt.

Dieses Muster lohnt sich für Ihre eigenen Testsuiten. Ein eingechecktes Testzertifikat läuft irgendwann ab, und wenn das geschieht, sieht der Fehler genau so aus wie der Bug, den dieses Release aufdecken soll.

Fazit

Drei Änderungen, eine Richtung: Fehler, die früher beim Empfänger auftraten, erscheinen jetzt beim Absender. Erneuern Sie das Zertifikat, anstatt AllowExpired zu verwenden, lassen Sie SHA-256 zum Standard werden, prüfen Sie eingehende Dokumente kryptografisch und wählen Sie ein LogLevel, bevor Sie es benötigen. Das Beispiel führt alle sechs Verhaltensweisen in einem Durchlauf aus, einschließlich der Ablehnung, sodass das Upgrade in wenigen Minuten geprobt werden kann.

Zusätzliche Ressourcen