💡 Esempio completo funzionante disponibile su GitHub:
pdf-firma-controlli-certificato-python
Introduzione
Un servizio firma i PDF caricati ogni notte. Una mattina il certificato che utilizza supera la data di scadenza e nulla sembra cambiare: il lavoro viene eseguito, i file vengono scritti, il log sembra normale. Alcune settimane dopo qualcuno apre uno di quei documenti in Acrobat e vede un banner di avviso, perché una firma effettuata con un certificato scaduto non è una firma più debole – è una firma che i validatori segnalano come non valida. I documenti che sembrano approvati valgono meno di quelli non firmati, perché le persone vi hanno creduto.
Quel rifiuto ha un nome. Il controllo della validità del certificato è un comportamento di GroupDocs.Signature per Python che rifiuta di firmare una volta che il periodo di validità del certificato è scaduto, o prima che inizi. È arrivato nella versione 26.9 insieme a due modifiche con la stessa forma: SHA‑256 è diventato il digest predefinito per le firme PDF, e SignatureSettings.log_level ha iniziato a filtrare invece di essere silenziosamente ignorato. Ognuna di esse prende un risultato che prima avveniva in modo silenzioso e lo mette davanti a te.
Questo articolo confronta quei tre controlli così come si comportano da Python tramite .NET – cosa cambia in output, quando usarli e quali due dettagli del binding costano un pomeriggio alle persone. Ogni risultato citato proviene dall’esecuzione del campione su un PDF di una pagina.
Perché Questo È Più Importante di una Nota di Versione
Le tre modifiche condividono una proprietà che vale la pena nominare: tutte convertono un errore che avresti scoperto più tardi in uno che scopri subito.
- Certificati scaduti: la chiamata di firma fallisce dove qualcuno può rinnovare il certificato, invece di produrre documenti che falliscono la validazione dopo la distribuzione
- Digest predefiniti: le nuove firme usano SHA‑256 senza che nessuno debba ricordarsi di richiederlo, così l’opzione debole richiede una decisione anziché una distrazione
- Livelli di log: un servizio che configura solo avvisi ora riceve solo avvisi, rendendo gli avvisi leggibili, il che significa che vengono letti
Quest’ultimo è meno cosmetico di quanto sembri. Il valore dell’avviso sul certificato scaduto è che qualcuno lo vede, e un avviso sepolto tra dieci messaggi di trace per esecuzione di firma è un avviso che nessuno vede.
Prerequisiti
Prima di iniziare, assicurati di avere:
- Python 3.9 o successivo su un interprete a 64 bit – il pacchetto include un runtime .NET incorporato e non ha una wheel a 32 bit
- GroupDocs.Signature per Python via .NET 26.10.0, con una licenza temporanea gratuita se vuoi rimuovere i limiti di valutazione
- Un PDF da firmare e il pacchetto
cryptographyse vuoi generare certificati di test usa‑e‑getta come fa il campione
Installazione
pip install groupdocs-signature-net cryptography
Controllo 1 – Il digest scritto nella firma
hash_algorithm su DigitalSignOptions sceglie il digest. Il valore predefinito dalla 26.9 è SHA‑256, nel formato adbe.pkcs7.detached che gli attuali validatori si aspettano; prima di quella versione, le nuove firme erano 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)
Due dettagli meritano di essere evidenziati. Il certificato arriva tramite certificate_stream come un io.BytesIO anziché come percorso di file, il che è il modo in cui un PKCS#12 costruito in memoria raggiunge la libreria senza mai essere scritto su disco – il campione si basa su questo per non includere alcuna chiave privata. E HashAlgorithm offre AUTO, SHA1, SHA256, SHA384 e SHA512, dove un timestamp, se aggiunto, usa il digest che la firma ha utilizzato.
In pratica questo è il controllo che tocchi meno. Il valore predefinito è già la risposta corretta, SHA384 e SHA512 esistono per quando una politica di firma li richiede, e SHA1 è un’impostazione di compatibilità per validatori che non puoi modificare.
Controllo 2 – Se un certificato scaduto ti blocca
Senza sovrascritture, firmare con un certificato il cui periodo di validità è terminato – o non è ancora iniziato – genera GroupDocsSignatureException e non scrive nulla.
try:
sign.sign(output_path, options)
return True
except signature.GroupDocsSignatureException as error:
print(f"Rejected: {str(error).splitlines()[0]}")
return False
Il messaggio indica il certificato, la data di scadenza, il thumbprint e la proprietà che lo permetterebbe, il che è sufficiente a un’applicazione per dire a un operatore cosa rinnovare. Prendere solo la prima riga è importante in Python: il testo dell’eccezione continua con lo stack trace .NET dal binding, e non è qualcosa da mostrare all’utente.
Quando hai davvero bisogno di firmare comunque – un test su un certificato archiviato, o un batch che deve essere eseguito stasera mentre il rinnovo è in corso – la sovrascrittura è per chiamata:
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 ha la stessa forma per un certificato emesso per una data successiva, e i due flag sono indipendenti: consentire un certificato scaduto non consente uno anticipato. Un certificato anticipato di solito indica che l’orologio della macchina è sbagliato piuttosto che che il certificato sia anomalo, e un orologio errato rende ogni firma prodotta da quella macchina dubbia, quindi verifica prima di sovrascrivere qualsiasi cosa.
Entrambe le sovrascritture emettono un avviso anziché passare silenziosamente, ed è la parte che si collega al terzo controllo.
Controllo 3 – Se qualcuno se ne accorge
SignatureSettings.log_level è un valore di flag. Il campione firma lo stesso documento tre volte, con LogLevel.NONE, LogLevel.WARNING | LogLevel.ERROR e LogLevel.ALL, contando ciò che arriva:
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)
I conteggi risultano: niente, poi un avviso, poi quell’avviso più dieci trace. Prima della 26.9 tutte e tre le righe sarebbero state identiche, perché il livello veniva accettato e ignorato – cosa utile da sapere se mai ne imposti uno, non vedi cambiamenti e concludi di aver interpretato male il tuo codice.
Due dettagli del binding mi sono costati un pomeriggio, quindi vale la pena dichiararli chiaramente. SignatureSettings.logger è read‑only, quindi il logger è un argomento del costruttore e assegnarlo genera AttributeError; log_level si imposta normalmente dopo. E un logger personalizzato non deve estendere groupdocs.signature.logging.ILogger – quella classe base avvolge un oggetto nativo il cui costruttore richiede un handle posseduto dalla libreria, quindi l’estensione genera TypeError. Il binding accetta qualsiasi oggetto semplice che fornisca i tre metodi:
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)
Fornisci a error e warning un parametro opzionale exception. La libreria non lo passa sempre, e un logger che lo richiede rompe sui messaggi che lo omettono.
Confronto dei Tre: Quando Usare Ognuno
| Controllo | Ideale per | Vantaggi principali | Limitazioni |
|---|---|---|---|
hash_algorithm |
soddisfare una politica che specifica un digest | un’unica assegnazione; stessa dimensione dell’output | inutile se il certificato stesso non è affidabile |
| controllo di validità e sovrascritture | qualsiasi firma per altri utenti | il fallimento avviene dove può essere corretto | una sovrascrittura produce un file, non uno affidabile |
log_level |
servizi i cui log sono già affollati | undici messaggi diventano uno | filtra solo il logging, mai le eccezioni |
Non sono alternative – una singola chiamata di firma utilizza tutti e tre. L’ordine di considerazione è quello delle conseguenze: il controllo di validità decide se esiste un file, il digest decide cosa contiene, e il livello di log decide chi lo sa.
Il Livello di Log Cambia le Eccezioni Che Ricevo?
No. Decide quali messaggi arrivano al tuo logger e nient’altro. Un certificato scaduto genera ancora GroupDocsSignatureException con LogLevel.NONE, e allow_expired firma ancora con LogLevel.ALL; i valori di ritorno e le eccezioni sono identici a tutti i livelli. Ciò che cambia è se l’avviso che spiega una firma dubbia viene mai letto da una persona.
Verifica Spostata nella Stessa Direzione
Vale la pena menzionarlo perché è l’altra metà della stessa release. verify con un DigitalVerifyOptions vuoto ora controlla ogni firma digitale PDF a livello crittografico, così un documento modificato dopo la firma risulta non valido anziché semplicemente inspiegabile:
with signature.Signature(signed_path) as sign:
result = sign.verify(DigitalVerifyOptions())
return result.is_valid
Due righe, e vale aggiungerle a qualsiasi pipeline che firma e poi archivia. Nota cosa non promette un True: dice che la firma corrisponde al documento, non che l’emittente sia affidabile. I certificati autofirmati del campione vengono verificati qui e sono comunque rifiutati da un lettore PDF, il che risponde separatamente alla questione della fiducia.
Buone Pratiche e Suggerimenti
- Mantieni il rifiuto come impostazione predefinita in tutto ciò che firma per conto degli utenti, e sovrascrivi per chiamata anziché globalmente. L’eccezione è poco costosa; un batch di firme non valide non lo è.
- Registra il testo dell’avviso, non solo un contatore. Nomina il certificato e la data, l’unica parte su cui un operatore può intervenire.
- Controlla l’orologio prima di consentire un certificato non ancora valido. Il certificato è di solito corretto e la macchina è spesso sbagliata, e ciò influisce su più di una chiamata di firma.
- Tieni i trace fuori dalla produzione. Circa dieci per esecuzione di firma si accumulano rapidamente; attivali durante la diagnosi e disattivali dopo.
- Verifica dopo la firma in qualsiasi pipeline, ora che il controllo è crittografico, così un output corrotto viene intercettato prima che il destinatario lo scopra.
Conclusione
Tre controlli, una chiamata di firma, e la stessa idea di design dietro tutti loro: l’esito rischioso ora richiede una decisione, e quello sicuro non ne richiede alcuna. Mantieni il controllo di validità, tratta allow_expired come un’eccezione per chiamata da registrare, lascia il digest invariato a meno che una politica non dica il contrario, e imposta un livello di log che renda gli avvisi leggibili.
Eseguire il campione su uno dei tuoi PDF richiede un minuto e stampa esattamente ciò che ogni controllo ha modificato – sei file firmati, un rifiuto deliberato e tre righe di conteggi di messaggi che non sono più uguali.