Una firma su un PDF non vieta le modifiche successive. Fissa un intervallo di byte, e un aggiornamento incrementale aggiunge nuovi byte dopo di esso, così la firma resta matematicamente valida mentre il documento acquisisce nuovo contenuto. Se quel contenuto sia accettabile è una questione di policy, e DocMDP è il punto in cui l'autore dichiara la policy: nessuna modifica, solo compilazione di moduli e firma, oppure queste più annotazioni. Farla rispettare significa classificare cosa sia realmente cambiato, che è ciò che fa AnalyzeModifications. Si punta a una revisione precedente, poi si legge GetModificationLevel per il verdetto complessivo e le accessors per singolo riscontro per livello, numero oggetto e descrizione di ogni differenza
Con questo in posto, l'applicazione di DocMDP si riduce a un confronto: il livello calcolato è al di sotto o uguale al livello che la policy consente
Perché un PDF firmato può legittimamente cambiare
Tre casi legittimi, e coprono la maggior parte di ciò che si vedrà. Un secondo firmatario aggiunge la propria firma. Un destinatario compila i campi modulo che l'autore ha lasciato aperti. E il materiale di validazione a lungo termine viene aggiunto: risposte OCSP e CRL scritte nel document security store del documento perché la firma resti verificabile quando i responder non ci sono più. Quest'ultimo caso non è semplicemente permesso: è ciò che un archivio ben gestito fa di proposito ai documenti firmati
Quindi "il file è cresciuto dopo la firma" non porta alcuna informazione. La domanda è sempre cosa sia stato aggiunto, e la risposta deve venire dal confronto degli stati del documento piuttosto che dall'osservazione dei byte. La meccanica di append in sé è trattata nell'articolo sugli aggiornamenti incrementali
Classificare per la forma dell'oggetto, non per il percorso che l'ha prodotto
Il classificatore guarda ciò che un oggetto è dopo la modifica, non quale chiamata di libreria l'ha creato. È deliberato, perché l'analisi gira su file prodotti da altro software, dove non c'è alcun percorso di chiamata da ispezionare
Quattro forme sono riconosciute. I dizionari di informazione del document security store e legati alla validazione, gli oggetti cross-reference stream, la voce metadata del catalogo e i dizionari di firma che trasportano un byte range sono materiale di archiviazione a lungo termine. Un oggetto che trasporta sia un tipo di campo sia un valore di campo è compilazione di moduli. Un oggetto il cui tipo è annotation, o il cui subtype è uno di quelli elencati nella Tabella 168 di ISO 32000-2, è una modifica di annotazione. Tutto il resto è unclassified
Le rimozioni sono trattate più severamente delle aggiunte. Un oggetto rimosso è inserito in whitelist solo quando l'oggetto sul lato vecchio era esso stesso materiale di archivio, il che copre il caso normale di un security store sostituito da uno più recente. Ogni altra rimozione è unclassified, perché cancellare contenuto da un documento firmato non è qualcosa che un livello di permessi autorizza. Le differenze a livello di documento sono più severe ancora: una modifica nel numero di pagine va dritta a unclassified senza esaminare i singoli oggetti, poiché nessun livello DocMDP consente di aggiungere o togliere pagine
La whitelist sbaglia verso il rifiuto
Questa è la regola di progetto che governa ogni decisione di confine. Una modifica classificata per errore come permessa è una firma che valida su contenuto che l'autore non ha mai autorizzato. Una modifica classificata per errore come unclassified è un documento che viene segnalato e revisionato da una persona. Quei due errori non sono simmetrici, quindi la whitelist resta stretta e le forme non riconosciute ricadono in unclassified invece di essere indovinate
C'è una conseguenza pratica da prevedere: i file di produttori insoliti a volte segnaleranno modifiche unclassified che, a un esame, sono benigne. La risposta giusta è guardare il dettaglio del riscontro e il numero oggetto piuttosto che allargare la whitelist, perché una whitelist che cresce per silenziare singoli report smette di essere un controllo di sicurezza
uses
PDFlibrary, PDFlibCompare;
var
Pdf: TPDFlib;
I, Level: Integer;
begin
Pdf := TPDFlib.Create(nil);
try
Pdf.LoadFromFile('contract-countersigned.pdf', '');
if Pdf.AnalyzeModifications('contract-as-signed.pdf', '') < 0 then
raise Exception.Create('the earlier revision could not be loaded');
// TPLModificationLevel ordinato mlNone, mlLTAUpdates, mlFormFilling,
// mlAnnotations, mlUnclassified; il getter restituisce il suo ordinale
Level := Pdf.GetModificationLevel;
// l'applicazione di DocMDP è ora un solo confronto con la policy
if Level > Ord(mlFormFilling) then
for I := 0 to Pdf.GetModificationFindingCount - 1 do
Report.Add(Format('object %d, level %d: %s',
[Pdf.GetModificationFindingObjNum(I),
Pdf.GetModificationFindingLevel(I),
Pdf.GetModificationFindingDetail(I)]));
finally
Pdf.Free;
end;
end;
Il livello complessivo è il massimo su tutti i riscontri, che è l'unica aggregazione difendibile: un documento che contiene novantanove aggiunte di archivio e una modifica unclassified è una modifica unclassified
Sotto il cofano: fingerprint, non hash crittografici
Il motore di confronto che CompareWith espone, e su cui l'analisi delle modifiche è costruita, identifica gli oggetti tramite un fingerprint dei loro corpi normalizzati usando un hash a 64 bit non crittografico anziché SHA-256. È una scelta meditata. Ciò che serve al confronto strutturale è il determinismo: lo stesso corpo di oggetto deve produrre sempre lo stesso fingerprint all'interno di un'esecuzione. Non serve la resistenza alle collisioni, perché un attaccante che controlla entrambi i lati del confronto ha già vinto con altri mezzi, e pagare un hash crittografico completo su ogni oggetto in un documento da un milione di oggetti è un costo reale senza beneficio
Due regole di normalizzazione contano più della scelta dell'hash. I riferimenti indiretti si ripiegano in un token segnaposto invece di essere espansi nel contenuto referenziato: espanderli copierebbe il corpo di un oggetto condiviso in ogni referenziante, così una piccola modifica a un font descriptor condiviso invaliderebbe il fingerprint di ogni oggetto che lo raggiunge, e il report sarebbe illeggibile. E i numeri di oggetto stessi sono esclusi dal fingerprint, perché una riscrittura può rinumerare gli oggetti senza cambiare nulla di semantico
L'accoppiamento poi gira in due passate, allineando prima per fingerprint e accoppiando il resto per numero oggetto per identificare modifiche piuttosto che un'aggiunta più una rimozione. I controlli economici vengono sempre per primi: una differenza nel numero di pagine viene riportata prima che inizi qualsiasi attraversamento di oggetti
Una trappola: l'autoconfronto non è garantito identico
Il primo test naturale per un motore di diff è confrontare un file con se stesso e asserire che il risultato sia identico. Quell'asserzione qui non vale, e la ragione è istruttiva. Il percorso di caricamento pubblico e il percorso di caricamento documenti di livello più basso non configurano la decodifica allo stesso modo, così lo stesso file caricato attraverso le due vie può produrre fingerprint diversi per alcuni oggetti. Il motore non sbaglia; i due caricamenti hanno davvero prodotto stati in memoria diversi
Invece di forzare le due vie insieme, la semantica del confronto è dichiarata in modo ristretto: l'analisi confronta lo stato corrente del documento con una revisione precedente, e riporta identico solo quando i due insiemi di fingerprint coincidono esattamente. Questa è la domanda che gli utenti pongono davvero, e non richiede che i due loader siano intercambiabili. Quando si progetta una funzione di confronto, definire cosa significhi "lo stesso" è più lavoro che calcolarlo
Dove usarlo
Due posti. In un report di validazione, accanto alla verifica delle firme, così un revisore vede non solo se la firma è crittograficamente intatta ma cosa sia accaduto al documento dopo; il lato firme è trattato in firma e validazione PAdES. E in una porta di ingresso, dove un documento che arriva dall'esterno viene confrontato con la copia inviata, così un contratto restituito con un'annotazione aggiunta è trattato diversamente da uno con una pagina modificata
Un avvertimento sull'ambito. Questa analisi dice cosa sia cambiato tra due revisioni della stessa linea di documenti. Non dice se il contenuto visibile sia fuorviante, se lo appearance stream di un campo modulo corrisponda al suo valore, o se testo nascosto sotto un overlay sia ancora presente nel content stream. Quei casi richiedono un trattamento separato, e il lato rimozione del contenuto è trattato nell'articolo sulla redaction vera. I punti di ingresso di analisi e confronto sono documentati nella pagina di prodotto di losLab PDF Developer Library