💡 Volledig werkend voorbeeld beschikbaar op GitHub:
pdf-signing-certificate-checks-python

Introductie

Een service ondertekent elke nacht geüploade PDF‑bestanden. Op een ochtend is het certificaat dat ze gebruiken verlopen, en er lijkt niets te veranderen: de taak wordt uitgevoerd, de bestanden worden weggeschreven, het logboek ziet er normaal uit. Enkele weken later opent iemand een van die documenten in Acrobat en ziet een waarschuwingsbanner, omdat een handtekening gemaakt met een verlopen certificaat niet een zwakkere handtekening is – het is er één die validators als ongeldig rapporteren. De documenten die er goedgekeurd uitzien, zijn minder waard dan onondertekende, omdat mensen er in geloofden.

Die weigering heeft een naam. Certificaat‑geldigheidscontrole is een GroupDocs.Signature‑gedrag voor Python dat weigert te ondertekenen zodra de geldigheidsperiode van het certificaat is verstreken, of nog niet is begonnen. Het kwam in versie 26.9 samen met twee wijzigingen met dezelfde vorm: SHA‑256 werd de standaard‑digest voor PDF‑handtekeningen, en SignatureSettings.log_level begon te filteren in plaats van stil te worden genegeerd. Elk van deze wijzigingen neemt een resultaat dat stilletjes gebeurde en brengt het naar de voorgrond.

Dit artikel vergelijkt die drie controles zoals ze zich gedragen vanuit Python via .NET – wat elk van hen verandert in de output, wanneer je ze moet gebruiken, en welke twee details van de binding mensen een middag kosten. Elk geciteerd resultaat komt van het uitvoeren van het voorbeeld tegen een één‑pagina‑PDF.

Waarom dit belangrijker is dan een versie‑opmerking

De drie wijzigingen delen een eigenschap die benoemd moet worden: ze zetten allemaal een fout die je later zou ontdekken om in een fout die je nu ontdekt.

  • Verlopen certificaten: de ondertekeningsaanroep faalt waar iemand het certificaat kan vernieuwen, in plaats van documenten te produceren die na distributie niet meer geldig zijn
  • Digest‑standaarden: nieuwe handtekeningen gebruiken SHA‑256 zonder dat iemand eraan hoeft te denken, zodat de zwakke optie een bewuste beslissing vereist in plaats van onoplettendheid
  • Log‑niveaus: een service die alleen waarschuwingen configureert ontvangt nu alleen waarschuwingen, waardoor de waarschuwingen leesbaar worden, wat betekent dat ze worden gelezen

Die laatste is minder cosmetisch dan het klinkt. De hele waarde van de waarschuwing voor een verlopen certificaat is dat iemand het ziet, en een waarschuwing begraven tussen tien trace‑berichten per ondertekeningsrun is een waarschuwing die niemand ziet.

Voorvereisten

Zorg ervoor dat je het volgende hebt:

  • Python 3.9 of later op een 64‑bit interpreter – het pakket levert een gebundelde .NET‑runtime en heeft geen 32‑bit wheel
  • GroupDocs.Signature voor Python via .NET 26.10.0, met een gratis tijdelijke licentie als je de evaluatielimieten wilt verwijderen
  • Een PDF om te ondertekenen, en het cryptography‑pakket als je test‑certificaten wilt aanmaken zoals het voorbeeld doet

Installatie

pip install groupdocs-signature-net cryptography

Controle 1 – De digest die in de handtekening wordt geschreven

hash_algorithm op DigitalSignOptions kiest de digest. De standaard sinds 26.9 is SHA‑256, in het adbe.pkcs7.detached‑formaat dat huidige validators verwachten; daarvoor waren nieuwe handtekeningen 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)

Twee details verdienen extra aandacht. Het certificaat komt via certificate_stream als een io.BytesIO in plaats van een bestandspad, wat de manier is waarop een in‑memory PKCS#12‑bestand de bibliotheek bereikt zonder ooit naar schijf te worden geschreven – het voorbeeld vertrouwt hierop zodat er helemaal geen privésleutel wordt meegeleverd. En HashAlgorithm biedt AUTO, SHA1, SHA256, SHA384 en SHA512, waarbij een tijdstempel, als je die toevoegt, de digest gebruikt die de handtekening heeft gebruikt.

In de praktijk is dit de controle die je het minst aanraakt. De standaard is al het juiste antwoord, SHA384 en SHA512 bestaan voor wanneer een ondertekeningsbeleid ze benoemt, en SHA1 is een compatibiliteitsinstelling voor validators die je niet kunt wijzigen.

Controle 2 – Of een verlopen certificaat je stopt

Zonder overrides veroorzaakt ondertekenen met een certificaat waarvan de geldigheidsperiode is verlopen – of nog niet is begonnen – een GroupDocsSignatureException en wordt er niets weggeschreven.

try:
    sign.sign(output_path, options)
    return True
except signature.GroupDocsSignatureException as error:
    print(f"Rejected: {str(error).splitlines()[0]}")
    return False

Het bericht noemt het certificaat, de datum waarop het verlopen is, de vingerafdruk en de eigenschap die het zou toestaan, wat voldoende is voor een applicatie om een operator te laten weten wat vernieuwd moet worden. Alleen de eerste regel meenemen is in Python specifiek belangrijk: de tekst van de uitzondering gaat verder met de .NET‑stacktrace achter de binding, en dat is niets om aan een gebruiker te tonen.

Wanneer je echt toch moet ondertekenen – een test tegen een gearchiveerd certificaat, of een batch die vanavond moet draaien terwijl de vernieuwing onderweg is – is de override per aanroep:

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 heeft dezelfde vorm voor een certificaat dat voor een latere datum is uitgegeven, en de twee vlaggen zijn onafhankelijk: het toestaan van een verlopen certificaat staat een vroeg certificaat niet toe. Een vroeg certificaat betekent meestal dat de klok van de machine verkeerd staat in plaats van dat het certificaat ongebruikelijk is, en een verkeerde klok maakt elke handtekening die die machine produceert twijfelachtig, dus controleer dat voordat je iets overschrijft.

Beide overrides geven een waarschuwing in plaats van stilletjes door te gaan, wat het onderdeel is dat aansluit op de derde controle.

Controle 3 – Of iemand het ontdekt

SignatureSettings.log_level is een vlag‑waarde. Het voorbeeld ondertekent hetzelfde document drie keer, onder LogLevel.NONE, LogLevel.WARNING | LogLevel.ERROR en LogLevel.ALL, en telt wat er binnenkomt:

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)

De tellingen komen uit als niets, dan één waarschuwing, dan die waarschuwing plus tien traces. Voor 26.9 zouden alle drie de rijen identiek zijn geweest, omdat het niveau werd geaccepteerd en genegeerd – iets om te weten als je ooit een niveau hebt ingesteld, geen verandering zag, en concludeerde dat je je eigen code verkeerd had gelezen.

Twee details van de binding kostten me een middag, dus ze zijn het waard om duidelijk te benoemen. SignatureSettings.logger is alleen‑lezen, dus de logger moet als constructor‑argument worden meegegeven en toewijzing eraan veroorzaakt een AttributeError; log_level wordt daarna normaal ingesteld. En een aangepaste logger mag niet afleiden van groupdocs.signature.logging.ILogger – die basisklasse omsluit een native object waarvan de constructor een handle nodig heeft die de bibliotheek bezit, dus afleiden veroorzaakt een TypeError. De binding accepteert elk gewoon object dat de drie methoden levert:

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)

Geef error en warning een optionele exception‑parameter. De bibliotheek geeft niet altijd een uitzondering door, en een logger die er een vereist, breekt bij de berichten die er geen hebben.

De drie vergelijken: wanneer gebruik je welke

Controle Het beste voor Belangrijkste voordelen Beperkingen
hash_algorithm het naleven van een beleid dat een digest benoemt één toewijzing; dezelfde outputgrootte zinloos als het certificaat zelf niet vertrouwd wordt
geldigheidscontrole en overrides alles wat voor anderen ondertekent fout landt waar hij kan worden verholpen een override produceert een bestand, maar niet een betrouwbaar één
log_level services waarvan de logs al druk zijn elf berichten worden één filtert alleen logging, nooit uitzonderingen

Het zijn geen alternatieven – één ondertekeningsaanroep gebruikt alle drie. De volgorde om erover na te denken is de volgorde van consequentie: de geldigheidscontrole bepaalt of er een bestand bestaat, de digest bepaalt wat erin zit, en het log‑niveau bepaalt wie het weet.

Verandert het log‑niveau welke uitzonderingen ik krijg?

Nee. Het bepaalt welke berichten je logger bereiken en niets meer. Een verlopen certificaat werpt nog steeds GroupDocsSignatureException onder LogLevel.NONE, en allow_expired ondertekent nog steeds onder LogLevel.ALL; retourwaarden en uitzonderingen zijn identiek over elk niveau. Wat verandert is of de waarschuwing die een twijfelachtige handtekening verklaart ooit door een persoon wordt gelezen.

Verificatie verplaatst in dezelfde richting

Het is het vermelden waard omdat het de andere helft van dezelfde release is. verify met een lege DigitalVerifyOptions controleert nu elke digitale PDF‑handtekening cryptografisch, zodat een document dat na ondertekening is aangepast ongeldig terugkomt in plaats van slechts onverklaard:

with signature.Signature(signed_path) as sign:
    result = sign.verify(DigitalVerifyOptions())
    return result.is_valid

Twee regels, en het is de moeite waard om toe te voegen aan elke pipeline die ondertekent en daarna opslaat. Let op wat een True niet belooft: het zegt dat de handtekening overeenkomt met het document, niet dat de uitgever vertrouwd wordt. De zelfondertekende certificaten van het voorbeeld verifiëren hier en worden nog steeds geweigerd door een PDF‑lezer, wat de vertrouwensvraag apart beantwoordt.

Best practices en tips

  • Behoud de weigering als standaard in alles wat namens gebruikers ondertekent, en overschrijf per aanroep in plaats van globaal. De uitzondering is goedkoop; een batch met ongeldige handtekeningen is dat niet.
  • Log de waarschuwings‑tekst, niet alleen een teller. Het noemt het certificaat en de datum, wat het enige deel is waarop een operator kan handelen.
  • Controleer de klok voordat je een nog‑niet‑geldig certificaat toestaat. Het certificaat is meestal juist en de machine meestal fout, en dat beïnvloedt meer dan één ondertekeningsaanroep.
  • Houd traces uit productie. Rond de tien per ondertekeningsrun stapelen zich snel op; zet ze aan tijdens diagnostiek en uit daarna.
  • Verifieer na ondertekenen in elke pipeline, nu de controle cryptografisch is, zodat een beschadigde output wordt opgevangen voordat een ontvanger het ontdekt.

Conclusie

Drie controles, één ondertekeningsaanroep, en hetzelfde ontwerpidee achter alledrie: het riskante resultaat vereist nu een beslissing, en het veilige vereist niets. Houd de geldigheidscontrole, behandel allow_expired als een per‑aanroep‑uitzondering die je logt, laat de digest ongemoeid tenzij een beleid iets anders zegt, en stel een log‑niveau in dat de waarschuwingen leesbaar maakt.

Het uitvoeren van het voorbeeld tegen een eigen PDF duurt een minuut en print precies wat elke controle heeft veranderd – zes ondertekende bestanden, één bewuste weigering, en drie rijen met berichtentellingen die niet meer gelijk zijn.

Aanvullende bronnen