💡 Esempio completo funzionante disponibile su GitHub:
digital-signing-certificate-validity-dotnet

Il problema di conformità che nessuno vede finché non interviene un revisore

Un servizio di firma funziona per tre anni senza errori. I documenti vengono inviati, i destinatari li accettano, nulla nei log suggerisce un problema. Poi il validatore di una controparte segnala un lotto come non valido, e l’indagine individua due cause: le firme sono state create con SHA‑1 e, negli ultimi quattro mesi, il certificato era scaduto.

Entrambi i fallimenti erano silenziosi al momento della firma. È questo che cambia in GroupDocs.Signature 26.9.

L’applicazione del periodo di validità del certificato è il nuovo comportamento predefinito per la firma digitale in .NET: un certificato al di fuori della sua finestra di validità viene rifiutato anziché utilizzato. Arriva con due compagni – SHA‑256 come digest PDF predefinito e un LogLevel che finalmente filtra – e insieme spostano tre classi di errore dal destinatario al mittente, dove possono ancora essere corrette.

Perché il “successo silenzioso” è l’esito più costoso

La firma è insolita perché la parte che commette l’errore non è quella che lo scopre. Una fattura malformata fallisce nel tuo sistema; una firma non valida fallisce nel sistema di qualcun altro, settimane dopo, senza alcuna diagnostica leggibile.

Questa asimmetria è il motivo per cui “l’API ha restituito successo” non è una garanzia utile qui. Le impostazioni predefinite precedenti erano ottimizzate per non interrompere il chiamante, e il costo ricadeva sul destinatario e, alla fine, su chi doveva rifirmare e reinviare centinaia di documenti.

Modifica 1: i certificati scaduti vengono rifiutati

Il cambiamento principale. Sign ora lancia GroupDocsSignatureException quando la validità del certificato è terminata o non è ancora iniziata, e nulla viene scritto su disco.

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

Il messaggio indica il certificato e la proprietà che lo avrebbe permesso, così un operatore che legge una riga di log può intervenire senza aprire la documentazione. Per una pipeline che passa a 26.9 e inizia a fallire, questa è quasi sempre la causa – e la risposta corretta è il rinnovo, non la soppressione.

Quando hai davvero bisogno del comportamento precedente, esiste una sola proprietà:

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

Il documento viene firmato e un avviso viene inviato al logger. I validatori rifiutano comunque il risultato, perché AllowExpired controlla ciò che la libreria permette, non il valore reale del certificato. Il flag complementare AllowNotYetValid copre l’estremità opposta della finestra ed è deliberatamente indipendente: consentire un certificato scaduto non permette silenziosamente uno con data futura.

Modifica 2: SHA‑256 per impostazione predefinita

Le firme digitali PDF ora vengono scritte con SHA‑256 nel formato adbe.pkcs7.detached che i validatori attuali si aspettano. Le versioni precedenti usavano SHA‑1.

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

Impostare esplicitamente la proprietà è necessario solo per andare oltre – Sha384 o Sha512 quando una policy lo richiede – o per rimanere su Sha1 per un validatore che non può gestire altro. Un timestamp aggiunto alla firma utilizza lo stesso digest.

La verifica è cambiata nella stessa release e nella stessa direzione: DigitalVerifyOptions senza criteri era quasi un no‑op, ora esegue un controllo crittografico completo, così un documento modificato dopo la firma viene segnalato come non valido.

Modifica 3: LogLevel filtra davvero

SignatureSettings accetta un logger da tempo. Prima della 26.9 il livello veniva ignorato, quindi ogni messaggio arrivava comunque e la maggior parte dei servizi disattivava il logging piuttosto che affogare nei trace.

Il campione rende la differenza misurabile firmando lo stesso documento tre volte con un logger contatore:

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

None produce zero messaggi, Warning | Error mantiene l’unico avviso generato dal certificato scaduto consentito, e All aggiunge un trace per ogni passaggio. Il logger contatore è il punto di integrazione per la tua stack:

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

Implementa questi tre metodi con Serilog, NLog o Application Insights e le diagnostiche della libreria arriveranno dove logghi il resto del servizio.

Il livello di log cambia le eccezioni che ricevo?

No, ed è importante essere espliciti perché le due cose sembrano correlate. LogLevel filtra ciò che raggiunge l’ILogger. Le eccezioni vengono lanciate al tuo codice comunque: un certificato scaduto senza AllowExpired genera comunque un’eccezione anche con LogLevel.None, e il tuo blocco catch si comporta identicamente. Diagnostica e flusso di controllo sono canali separati, ed è questo che rende sicuro eseguire la produzione con Warning | Error.

Il rifiuto è più economico di quanto sembri

L’obiezione a un blocco rigido è operativa: un batch notturno che prima terminava ora fallisce alle 02:00 e qualcuno viene avvisato.
Questo è un costo reale, ma è comunque il più piccolo. Un batch rifiutato genera un avviso, un rinnovo e una nuova esecuzione, tutto all’interno dei tuoi sistemi. Un batch firmato con un certificato scaduto viene scoperto dal destinatario, il che comporta un thread di supporto, una ri‑emissione di tutti i documenti interessati e una conversazione imbarazzante su quanto tempo sia passato.

Il campione rende il fallimento concreto anziché teorico: firma intenzionalmente con un certificato scaduto, cattura l’eccezione e stampa il messaggio, così puoi vedere esattamente cosa conterranno i tuoi log prima che l’aggiornamento arrivi in produzione. Ti consiglierei di eseguire quel metodo con il tuo archivio di certificati prima di programmare l’upgrade.

Cosa fare prima di aggiornare

Tre controlli, in ordine di probabilità di incorrere in problemi.

  1. Verifica la scadenza dei certificati su tutti i percorsi di firma, incluse le operazioni mensili o trimestrali – è lì che un certificato scaduto rimane più a lungo nascosto.
  2. Cerca HashAlgorithm: se nulla lo imposta, i tuoi digest passeranno da SHA‑1 a SHA‑256 con l’upgrade, un miglioramento che merita comunque una nota di rilascio.
  3. Decidi deliberatamente un livello di log. Il valore onesto per un servizio è Warning | Error; All serve per riprodurre un problema specifico, e None significa rinunciare al segnale unico che indica che una firma è stata creata con una deroga.

La verifica è cambiata nella stessa direzione

È facile perderlo di vista, perché il codice chiamante non deve cambiare. DigitalVerifyOptions senza criteri impostati era quasi un no‑op: confrontava i criteri forniti e, non avendone, aveva poco da dire. Dalla 26.9 la stessa chiamata esegue un controllo crittografico completo di ogni firma digitale PDF.

Per un servizio che verifica documenti in ingresso, questo è un upgrade silenzioso da “c’è una firma qui” a “questa firma corrisponde a questo contenuto”. È utile saperlo prima di vedere un documento fallire la verifica che il mese scorso era passato: il documento probabilmente è stato modificato, e il controllo più vecchio semplicemente non lo rilevava.

I certificati nel campione

Un dettaglio da copiare piuttosto che il codice: il campione non fornisce alcuna chiave privata. TestCertificates.cs genera tre PFX auto‑firmati in memoria al momento dell’esecuzione – valido, scaduto l’anno scorso, valido dall’anno prossimo – così la dimostrazione funziona indipendentemente dalla data odierna e non contiene nulla di sensibile nel repository.

Questo modello vale la pena adottarlo nei propri test. Un certificato di test committato scade inevitabilmente, e quando lo fa il fallimento appare esattamente come il bug che questa release intendeva far emergere.

Conclusione

Tre cambiamenti, una direzione: i fallimenti che prima apparivano al destinatario ora compaiono al mittente. Rinnova il certificato invece di ricorrere a AllowExpired, lascia che SHA‑256 sia il valore predefinito, verifica i documenti in ingresso in modo crittografico e scegli un livello di log prima di averne bisogno. Il campione esegue tutti e sei i comportamenti in un unico passaggio, incluso il rifiuto, così l’upgrade può essere provato in pochi minuti.

Risorse aggiuntive