💡 Voll funktionsfähiges Beispiel auf GitHub verfügbar:
pdf-signing-certificate-checks-python
Einführung
Ein Service signiert jede Nacht hochgeladene PDFs. Eines Morgens ist das Zertifikat, das er verwendet, abgelaufen, und es scheint sich nichts geändert zu haben: Der Job läuft, die Dateien werden geschrieben, das Log sieht normal aus. Wochen später öffnet jemand eines dieser Dokumente in Acrobat und sieht ein Warnbanner, weil eine Signatur mit einem abgelaufenen Zertifikat nicht eine schwächere Signatur ist – sie wird von Validierern als ungültig gemeldet. Die Dokumente, die als genehmigt erscheinen, sind weniger wert als unsignierte, weil die Menschen ihnen vertrauten.
Diese Ablehnung hat einen Namen. Die Prüfung der Zertifikatsgültigkeit ist ein Verhalten von GroupDocs.Signature für Python, das das Signieren ablehnt, sobald der Gültigkeitszeitraum des Zertifikats abgelaufen ist oder noch nicht begonnen hat. Es kam in Version 26.9 zusammen mit zwei Änderungen gleicher Art: SHA‑256 wurde zum Standard‑Digest für PDF‑Signaturen, und SignatureSettings.log_level begann zu filtern, anstatt stillschweigend ignoriert zu werden. Jede dieser Änderungen nimmt ein Ergebnis, das früher stillschweigend geschah, und stellt es Ihnen in den Vordergrund.
Dieser Artikel vergleicht diese drei Steuerungen, wie sie sich von Python über .NET verhalten – was jede von ihnen an der Ausgabe ändert, wann man sie einsetzen sollte und welche beiden Details der Bindung den Menschen einen Nachmittag gekostet haben. Jeder zitierte Wert stammt aus dem Ausführen des Beispiels gegen ein einseitiges PDF.
Warum das wichtiger ist als ein Versionshinweis
Die drei Änderungen teilen eine nennenswerte Eigenschaft: Sie verwandeln ein Versagen, das Sie später entdecken würden, in eines, das Sie jetzt entdecken.
- Abgelaufene Zertifikate: Der Signaturaufruf schlägt fehl, sodass jemand das Zertifikat erneuern kann, anstatt Dokumente zu erzeugen, die nach der Verteilung die Validierung nicht bestehen.
- Digest‑Standardwerte: Neue Signaturen verwenden SHA‑256, ohne dass jemand daran denken muss, etwas zu fragen, sodass die schwache Option eine bewusste Entscheidung erfordert statt Unaufmerksamkeit.
- Log‑Level: Ein Service, der nur Warnungen konfiguriert, erhält jetzt nur Warnungen, was die Warnungen lesbar macht, was wiederum bedeutet, dass sie gelesen werden.
Letzteres ist weniger kosmetisch, als es klingt. Der ganze Nutzen der Warnung bei abgelaufenem Zertifikat liegt darin, dass jemand sie sieht, und eine Warnung, die zwischen zehn Trace‑Nachrichten pro Signaturlauf vergraben ist, wird von niemandem gesehen.
Voraussetzungen
Stellen Sie vor dem Start sicher, dass Sie Folgendes haben:
- Python 3.9 oder neuer auf einem 64‑Bit‑Interpreter – das Paket liefert eine gebündelte .NET‑Runtime und hat kein 32‑Bit‑Wheel.
- GroupDocs.Signature für Python via .NET 26.10.0, mit einer kostenlosen temporären Lizenz, falls Sie die Evaluationsbeschränkungen entfernen möchten.
- Ein PDF zum Signieren und das Paket
cryptography, falls Sie wie im Beispiel temporäre Testzertifikate erzeugen wollen.
Installation
pip install groupdocs-signature-net cryptography
Steuerung 1 – Der Digest, der in die Signatur geschrieben wird
hash_algorithm auf DigitalSignOptions wählt den Digest. Der Standard seit 26.9 ist SHA‑256, im adbe.pkcs7.detached‑Format, das aktuelle Validierer erwarten; davor waren neue Signaturen SHA‑1.
with signature.Signature(source_path) as sign:
options = DigitalSignOptions()
options.certificate_stream = io.BytesIO(pfx)
options.password = PASSWORD
options.hash_algorithm = HashAlgorithm.SHA512
options.reason = "Approved"
result = sign.sign(output_path, options)
return len(result.succeeded)
Zwei Details sind erwähnenswert. Das Zertifikat gelangt über certificate_stream als io.BytesIO und nicht über einen Dateipfad, was bedeutet, dass ein im Speicher gebautes PKCS#12‑Objekt die Bibliothek erreicht, ohne jemals auf die Festplatte geschrieben zu werden – das Beispiel nutzt das, um überhaupt keinen privaten Schlüssel zu liefern. Und HashAlgorithm bietet AUTO, SHA1, SHA256, SHA384 und SHA512 an, wobei ein Zeitstempel, falls Sie einen hinzufügen, den Digest verwendet, den die Signatur selbst benutzt hat.
In der Praxis ist dies die Steuerung, die Sie am wenigsten berühren. Der Standard ist bereits die richtige Antwort, SHA384 und SHA512 existieren für den Fall, dass eine Signatur‑Richtlinie sie verlangt, und SHA1 ist eine Kompatibilitätseinstellung für Validierer, die Sie nicht ändern können.
Steuerung 2 – Ob ein abgelaufenes Zertifikat Sie stoppt
Ohne Overrides führt das Signieren mit einem Zertifikat, dessen Gültigkeitszeitraum beendet ist – oder noch nicht begonnen hat – zu einer GroupDocsSignatureException und es wird überhaupt nichts geschrieben.
try:
sign.sign(output_path, options)
return True
except signature.GroupDocsSignatureException as error:
print(f"Rejected: {str(error).splitlines()[0]}")
return False
Die Meldung nennt das Zertifikat, das Ablaufdatum, den Fingerabdruck und die Eigenschaft, die es erlauben würde – genug für eine Anwendung, um einem Operator zu sagen, was erneuert werden muss. Nur die erste Zeile zu nehmen ist in Python wichtig: Der Text der Ausnahme setzt sich mit dem .NET‑Stack‑Trace hinter der Bindung fort, und das sollte nicht dem Benutzer präsentiert werden.
Wenn Sie wirklich trotzdem signieren müssen – ein Test mit einem archivierten Zertifikat oder ein Batch, das heute Nacht laufen muss, während die Erneuerung noch aussteht – erfolgt der Override pro Aufruf:
settings = signature.SignatureSettings(ConsoleLogger())
settings.log_level = LogLevel.WARNING | LogLevel.ERROR
with signature.Signature(source_path, settings=settings) as sign:
options.allow_expired = True
result = sign.sign(output_path, options)
allow_not_yet_valid hat dieselbe Form für ein Zertifikat, das für ein späteres Datum ausgestellt wurde, und die beiden Flags sind unabhängig: Das Zulassen eines abgelaufenen Zertifikats erlaubt nicht das Zulassen eines vorzeitigen. Ein vorzeitiges Zertifikat bedeutet meist, dass die Systemuhr falsch ist, nicht dass das Zertifikat ungewöhnlich ist, und eine falsche Uhr macht jede Signatur, die die Maschine erzeugt, fragwürdig – prüfen Sie das also, bevor Sie irgendetwas überschreiben.
Beide Overrides geben eine Warnung aus, anstatt stillschweigend zu passieren, was den Bezug zur dritten Steuerung herstellt.
Steuerung 3 – Ob jemand es herausfindet
SignatureSettings.log_level ist ein Flag‑Wert. Das Beispiel signiert dasselbe Dokument dreimal, unter LogLevel.NONE, LogLevel.WARNING | LogLevel.ERROR und LogLevel.ALL und zählt, was ankommt:
logger = CollectingLogger()
settings = signature.SignatureSettings(logger)
settings.log_level = level
with signature.Signature(source_path, settings=settings) as sign:
options.allow_expired = True
sign.sign(output_path, options)
Die Zählungen ergeben nichts, dann eine Warnung, dann diese Warnung plus zehn Traces. Vor 26.9 wären alle drei Zeilen identisch gewesen, weil das Level akzeptiert und ignoriert wurde – das ist wichtig zu wissen, falls Sie jemals eines gesetzt haben, keine Änderung sahen und dachten, Sie hätten Ihren eigenen Code falsch gelesen.
Zwei Bindungsdetails kosteten mich einen Nachmittag, also sollten sie klar benannt werden. SignatureSettings.logger ist schreibgeschützt, daher wird der Logger als Konstruktor‑Argument übergeben und ein Zuweisen wirft AttributeError; log_level wird danach normal gesetzt. Und ein benutzerdefinierter Logger darf nicht von groupdocs.signature.logging.ILogger erben – diese Basisklasse umschließt ein natives Objekt, dessen Konstruktor einen von der Bibliothek besessenen Handle benötigt, sodass das Erben TypeError auslöst. Die Bindung marshalt jedes einfache Objekt, das die drei Methoden bereitstellt:
class StdlibLogger:
def error(self, message, exception=None):
logging.getLogger("groupdocs").error(message, exc_info=exception)
def warning(self, message, exception=None):
logging.getLogger("groupdocs").warning(message)
def trace(self, message):
logging.getLogger("groupdocs").debug(message)
Geben Sie error und warning einen optionalen exception‑Parameter. Die Bibliothek übergibt nicht immer einen, und ein Logger, der ihn verlangt, bricht bei Nachrichten, die ihn weglassen.
Vergleich der drei: Wann welche verwenden
| Steuerung | Am besten für | Hauptvorteile | Einschränkungen |
|---|---|---|---|
hash_algorithm |
Erfüllung einer Richtlinie, die einen Digest nennt | ein einziger Aufruf; gleiche Ausgabengröße | sinnlos, wenn das Zertifikat selbst nicht vertrauenswürdig ist |
| Gültigkeitsprüfung und Overrides | Alles, was für andere Personen signiert | Fehler tritt dort auf, wo er behoben werden kann | ein Override erzeugt eine Datei, aber keine vertrauenswürdige |
log_level |
Dienste, deren Logs bereits stark belastet sind | elf Nachrichten werden zu einer | filtert nur das Logging, nie Ausnahmen |
Sie sind keine Alternativen – ein einzelner Signaturaufruf nutzt alle drei. Die Reihenfolge, in der man über sie nachdenkt, entspricht ihrer Konsequenz: Die Gültigkeitsprüfung entscheidet, ob eine Datei existiert, der Digest entscheidet, was darin ist, und das Log‑Level entscheidet, wer es weiß.
Ändert das Log‑Level, welche Ausnahmen ich erhalte?
Nein. Es entscheidet nur, welche Nachrichten Ihren Logger erreichen und sonst nichts. Ein abgelaufenes Zertifikat löst weiterhin GroupDocsSignatureException unter LogLevel.NONE aus, und allow_expired signiert weiterhin unter LogLevel.ALL; Rückgabewerte und Ausnahmen sind über alle Level hinweg identisch. Was sich ändert, ist, ob die Warnung, die eine fragwürdige Signatur erklärt, jemals von einer Person gelesen wird.
Verifikation in dieselbe Richtung verschoben
Erwähnenswert, weil es die andere Hälfte desselben Releases ist. verify mit leeren DigitalVerifyOptions prüft jetzt jede digitale PDF‑Signatur kryptografisch, sodass ein nach dem Signieren verändertes Dokument als ungültig zurückkommt, anstatt nur unerklärt zu bleiben:
with signature.Signature(signed_path) as sign:
result = sign.verify(DigitalVerifyOptions())
return result.is_valid
Zwei Zeilen, und sie sollten in jede Pipeline aufgenommen werden, die signiert und dann speichert. Beachten Sie, was ein True nicht verspricht: Es sagt, dass die Signatur zum Dokument passt, nicht dass der Aussteller vertrauenswürdig ist. Die im Beispiel selbstsignierten Zertifikate verifizieren hier und werden dennoch von einem PDF‑Reader abgelehnt, was die Vertrauensfrage separat beantwortet.
Best Practices und Tipps
- Lassen Sie die Ablehnung als Standard in allem, was im Namen von Benutzern signiert, und überschreiben Sie pro Aufruf statt global. Die Ausnahme ist billig; ein Stapel ungültiger Signaturen ist es nicht.
- Loggen Sie den Warntext, nicht nur einen Zähler. Er nennt das Zertifikat und das Datum – das ist das einzige, worauf ein Operator reagieren kann.
- Prüfen Sie die Uhr, bevor Sie ein noch nicht gültiges Zertifikat zulassen. Das Zertifikat ist meist korrekt und die Maschine meist falsch, und das betrifft mehr als nur einen Signaturaufruf.
- Halten Sie Traces aus der Produktion fern. Rund zehn pro Signaturlauf summieren sich schnell; schalten Sie sie zur Diagnose ein und danach wieder aus.
- Verifizieren Sie nach dem Signieren in jeder Pipeline, jetzt wo die Prüfung kryptografisch ist, sodass ein beschädigtes Ergebnis gefangen wird, bevor ein Empfänger es entdeckt.
Fazit
Drei Steuerungen, ein Signaturaufruf und dieselbe Design‑Idee hinter allen: Das riskante Ergebnis erfordert jetzt eine Entscheidung, das sichere Ergebnis erfordert nichts. Behalten Sie die Gültigkeitsprüfung, behandeln Sie allow_expired als per‑Aufruf‑Ausnahme, die Sie loggen, lassen Sie den Digest unverändert, es sei denn, eine Richtlinie verlangt etwas anderes, und setzen Sie ein Log‑Level, das die Warnungen lesbar macht.
Das Ausführen des Beispiels gegen ein eigenes PDF dauert etwa eine Minute und gibt exakt aus, was jede Steuerung geändert hat – sechs signierte Dateien, eine bewusste Ablehnung und drei Zeilen mit Nachrichten‑Zählungen, die nicht mehr gleich aussehen.