HotPDF verifica le firme digitali nei documenti PDF caricati attraverso tre metodi di THotPDF: GetLoadedSignatureInfo, VerifyLoadedSignature, e VerifyLoadedSignatureEx, introdotti nella v2.259.0. Il componente ricalcola l'hash dei segmenti /ByteRange del file originale, controlla l'attributo CMS messageDigest ed esegue una verifica RSA PKCS#1 v1.5 rispetto al certificato del firmatario incorporato, restituendo svValid quando i byte del documento sono intatti
Lo scenario è ordinario ma la posta in gioco no. Una controparte restituisce un contratto firmato, il tuo flusso di lavoro deve archiviarlo e qualcuno pone l'unica domanda che conta: questo è lo stesso documento che abbiamo inviato, byte per byte, firmato con il certificato dichiarato? Rispondere a questa domanda nel codice rappresenta il lato di verifica del processo di firma; il lato di firma, ovvero la creazione e l'incorporamento delle firme PAdES in primo luogo, è trattato nell'articolo complementare sulla creazione di firme digitali PAdES con HotPDF. Questo articolo si concentra sulla direzione opposta: arriva un PDF già firmato e si desidera ottenere un verdetto programmatico piuttosto che uno screenshot del segno di spunta verde di Acrobat
In che modo un PDF firmato dimostra di non essere stato manomesso?
Una firma PDF protegge specifici intervalli di byte del file, non una nozione astratta di "documento". Lo standard ISO 32000-1 §12.8 definisce il meccanismo: il campo modulo della firma contiene un dizionario la cui voce /Contents ospita un contenitore CMS SignedData (RFC 5652), e il cui array /ByteRange indica le esatte regioni del file coperte dalla firma, secondo §12.8.1. L'array è un elenco di coppie di offset e lunghezza, in pratica due segmenti: tutto ciò che precede la stringa esadecimale /Contents e tutto ciò che la segue. Il valore della firma non può coprire se stesso, quindi l'hash del file viene calcolato escludendo tale spazio vuoto
Questo design ha una conseguenza che modella l'intera API: verifica deve calcolare l'hash sui byte serializzati originali, esattamente come risiedono su disco. Un modello di oggetti analizzato è inutile a questo scopo, poiché serializzare nuovamente anche un documento non modificato produce byte diversi. HotPDF esegue quindi la verifica rispetto al file sorgente da cui è stato caricato il documento, o rispetto a un TStream di byte grezzi fornito dall'utente, mai rispetto alla sua rappresentazione in memoria
Leggere i metadati della firma prima di avviare la verifica
La funzione GetLoadedSignatureInfo analizza il dizionario della firma e il suo contenitore CMS senza toccare un singolo byte del documento, il che la rende la prima chiamata ideale quando è necessario solo visualizzare chi ha firmato e quando. I campi firma sono indicizzati a partire da 0 nell'ordine dei campi modulo, e GetLoadedSignatureFieldCount indica quanti ne esistono. Il record THPDFSignatureInfo restituito contiene il nome del campo, /SubFilter, il nome comune del certificato del firmatario, i distinguished name del soggetto e dell'emittente, il numero di serie, le date di validità, l'ora della firma (dall'attributo firmato, se presente, altrimenti dalla voce /M del dizionario), il nome dell'algoritmo di digest e le stringhe /Reason, /Location e /ContactInfo. Il suo membro Status rimane svNotVerified, una descrizione corretta per "analizzato, non verificato"
var
Pdf: THotPDF;
Info: THPDFSignatureInfo;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('signed-contract.pdf');
for I := 0 to Pdf.GetLoadedSignatureFieldCount - 1 do
begin
Info := Pdf.GetLoadedSignatureInfo(I);
Writeln('Field: ', Info.FieldName);
Writeln('Signer: ', Info.SignerName);
Writeln('Issuer: ', Info.IssuerDN);
Writeln('Algorithm: ', Info.HashAlgorithm);
Writeln('SubFilter: ', Info.SubFilter);
end;
finally
Pdf.Free;
end;
end;
Eseguire la verifica crittografica
La funzione VerifyLoadedSignatureEx esegue la verifica completa per un documento caricato da file e restituisce il record informativo compilato in un'unica chiamata: riapre il file sorgente, calcola l'hash dei segmenti /ByteRange con l'algoritmo digest di SignerInfo, confronta il risultato con l'attributo firmato messageDigest (RFC 5652 §5.4), e verifica tramite RSA la firma sulla ricodifica DER SET degli attributi firmati. Quando una firma non contiene attributi firmati, il controllo RSA viene eseguito direttamente sull'hash del documento. Le firme supportate sono RSA PKCS#1 v1.5 con digest SHA-1, SHA-256, SHA-384 o SHA-512, coprendo i subfilter adbe.pkcs7.detached e ETSI.CAdES.detached prodotti dai principali strumenti di firma
var
Status: THPDFSignatureVerifyStatus;
Info: THPDFSignatureInfo;
begin
Status := Pdf.VerifyLoadedSignatureEx(0, Info);
case Status of
svValid:
if Info.CoversWholeDocument then
Writeln('Valid; signature covers the whole file')
else
Writeln('Valid; file was extended after signing');
svDigestMismatch:
Writeln('Document bytes changed after signing');
svSignatureInvalid:
Writeln('RSA check failed over signed attributes');
svUnsupportedAlgorithm:
Writeln('Non-RSA key or unknown digest algorithm');
svMalformed:
Writeln('CMS container could not be parsed');
svSourceUnavailable:
Writeln('No source bytes; use the TStream overload');
end;
end;
Due dettagli implementativi meritano di essere conosciuti perché spiegano fallimenti che potrebbero apparire misteriosi dall'esterno. In primo luogo, il controllo degli attributi firmati è rigido riguardo alla codifica: all'interno del file gli attributi sono taggati come [0] IMPLICIT, mas la firma è stata calcolata sulla loro forma DER SET OF, quindi il verificatore applica nuovamente il tag prima di calcolare l'hash, esattamente come richiesto da RFC 5652 §5.4. Un verificatore personalizzato che calcola l'hash sui byte così come appaiono nel file rifiuterà qualsiasi documento firmato correttamente. In secondo luogo, /Contents è convenzionalmente riempito di zeri fino a raggiungere una dimensione riservata in byte, quindi il verificatore tronca il blob DER alla lunghezza effettiva della sua SEQUENCE esterna prima dell'analisi; la presenza di zeri finali dall'aspetto anomalo è normale e non costituisce una corruzione del file. La stessa famiglia di problematiche nell'analisi ASN.1, sul lato dell'importazione dei certificati, è l'oggetto dell'articolo sul rafforzamento della sicurezza PKCS#12 e ASN.1 in HotPDF
Cosa garantisce effettivamente una firma valida?
Il valore svValid garantisce esattamente questo: l'hash dei byte indicati da /ByteRange corrisponde al valore firmato dal firmatario, e la firma è verificata con la chiave pubblica del certificato incorporato nel contenitore CMS. Si tratta dell'integrità dei byte più il collegamento alla chiave, e nulla di più. La convalida della catena di certificati e della fiducia è esplicitamente esclusa dal verificatore di HotPDF: esso non ripercorre la catena fino a una radice, non verifica la revoca e non consulta alcun archivio di fiducia. Un certificato autofirmato da un utente malintenzionato che ha rifirmato un documento modificato verrà comunque verificato come svValid, poiché la matematica interna è coerente. Decidere se il firmatario è chi dichiara di essere e se sia opportuno fidarsi è una scelta di policy che appartiene a un livello separato, sia esso la whitelist dei certificati dell'organizzazione, l'archivio certificati di Windows o un'autorità di convalida
Il flag CoversWholeDocument protegge da una lacuna più sottile. Una firma copre sempre e solo il suo /ByteRange, e il meccanismo di aggiornamento incrementale del PDF consente di aggiungere contenuti dopo una firma senza invalidarla, comportamento previsto a livello di progettazione e su cui si basano i flussi di lavoro multifirma. Il flag viene calcolato durante la verifica ed è vero solo quando i due segmenti più lo spazio vuoto di /Contents coprono l'intero file. Quando viene restituito svValid con CoversWholeDocument falso, la revisione firmata è intatta ma il file contiene aggiunte successive, e spetta al flusso di lavoro stabilire se tollerare o meno tali modifiche
I documenti caricati da stream e crittografati richiedono i propri byte sorgente
I metodi senza parametri VerifyLoadedSignature e VerifyLoadedSignatureEx dipendono dal fatto che il componente ricordi il file di provenienza del documento. Se si carica il documento da uno stream, non esiste un nome di file da riaprire; lo stesso vale dopo il ripristino con password per i documenti crittografati, il flusso descritto nell'articolo sulla crittografia PDF AES-256 con HotPDF. In entrambi i casi, gli overload basati su file restituiscono svSourceUnavailable anziché procedere per ipotesi. La soluzione è l'overload TStream, che consente di passare i byte grezzi originali da qualsiasi punto in cui siano memorizzati, un file esistente, un buffer di memoria o un blob del database
var
Src: TFileStream;
Status: THPDFSignatureVerifyStatus;
Info: THPDFSignatureInfo;
begin
// Stream-loaded document: the component holds no source
// file name, so supply the original bytes yourself.
Src := TFileStream.Create('signed-contract.pdf',
fmOpenRead or fmShareDenyWrite);
try
Status := Pdf.VerifyLoadedSignature(0, Src, Info);
if Status <> svValid then
Writeln('Verification failed: ', Ord(Status));
finally
Src.Free;
end;
end;
Segnalare ciò che non è possibile verificare
Un verificatore che conosce solo "valido" e "non valido" segnalerà in modo errato documenti che semplicemente non comprende, quindi l'enumerazione dello stato separa i casi che l'interfaccia utente dovrebbe distinguere. svDigestMismatch indica che i byte del documento sono cambiati dopo la firma, il classico segnale di manomissione. svSignatureInvalid indica che l'hash dei byte è corretto ma il controllo RSA è fallito, suggerendo un valore di firma corrotto o contraffatto. svUnsupportedAlgorithm è la risposta corretta per chiavi ECDSA e digest sconosciuti: la firma potrebbe essere valida, ma HotPDF non può verificarla, e segnalarla come "non valida" screditerebbe un documento integro. svMalformed segnala un contenitore CMS che non è stato possibile analizzare affatto. Per verifiche di tipo gate, VerifyAllLoadedSignatures restituisce vero solo quando esiste almeno un campo firma e ognuno di essi viene verificato come svValid, fornendo un comodo valore booleano singolo per una pipeline di acquisizione archivio che non accetta soluzioni parziali
La verifica delle firme, la firma PAdES, la crittografia AES-256 e le API di modifica dei documenti caricati sono tutte fornite nella stessa libreria VCL nativa per Delphi e C++Builder, senza dipendenze da DLL esterne; l'elenco completo delle funzionalità e le versioni supportate dell'IDE si trovano sulla pagina del prodotto HotPDF Component