Aggiornamenti incrementali: perché sono importanti
ISO 32000-2 §7.5.6 Evidence: Standard-backed
In sintesi
Sezione intitolata “In sintesi”Quando un PDF viene modificato dopo essere stato scritto, il modo corretto per salvarlo non è riscrivere il file. Si accodano invece gli oggetti modificati e una nuova sezione cross-reference in fondo al file, lasciando ogni byte originale esattamente al proprio posto. Questa pagina spiega come funziona e perché è proprio questa la ragione per cui una firma digitale può sopravvivere a una modifica successiva.
Perché è importante
Sezione intitolata “Perché è importante”Una firma protegge un intervallo di byte. Se il salvataggio di una modifica di una sola parola riscrivesse il file, ogni offset di byte si sposterebbe. L’intervallo firmato non descriverebbe più lo stesso contenuto. La firma si invaliderebbe, anche se il contenuto firmato fosse rimasto, di per sé, intatto.
Gli aggiornamenti incrementali servono a evitare proprio questo. I byte originali, inclusi quelli coperti da una firma, restano al loro posto. Un revisore può prendere un documento firmato e modificato in seguito e verificare la prima firma rispetto alla revisione originale. Il revisore vede esattamente ciò che è stato firmato e, separatamente, ciò che è cambiato dopo. Sbagliare questo punto significa invalidare firme valide o, peggio, perdere la capacità di dimostrare che cosa una firma abbia effettivamente attestato.
La versione breve
Sezione intitolata “La versione breve”- Un aggiornamento incrementale accoda: oggetti nuovi e modificati, poi una nuova sezione cross-reference, poi un nuovo trailer, tutto in fondo al file.
- Il contenuto originale del file viene lasciato intatto: non viene modificato sul posto.
- Il nuovo trailer contiene una voce
/Prev: l’offset di byte della sezione cross-reference precedente. Le sezioni formano una catena all’indietro. - Un lettore costruisce il proprio indice percorrendo quella catena a partire dalla più recente. Per qualsiasi numero di oggetto, prevale la voce più recente.
- Poiché nulla è stato sovrascritto, l’intervallo di byte coperto da una firma precedente è ancora identico byte per byte; quindi la firma resta verificabile ed è possibile recuperare il documento esattamente com’era stato firmato.
Come NextPDF lo affronta
Sezione intitolata “Come NextPDF lo affronta”NextPDF scrive il documento di base nel modo descritto dalla pagina precedente, poi espone i tre elementi necessari a un aggiornamento incrementale.
Dopo build(), il writer (src/Writer/PdfWriter.php) conserva:
- il buffer di output, recuperabile tramite
getBuffer(), così un aggiornamento può essere accodato esattamente alla fine dei byte già esistenti; - l’offset di byte dell’ultima sezione cross-reference, tramite
getLastXrefOffset(), che diventa il valore/Prevdella nuova sezione; - le voci del dizionario del catalogo, tramite
getCatalogEntries(), così un aggiornamento che deve riemettere il catalogo (per esempio per allegare un riferimento a una firma) non perde alcuna chiave precedente.
Una revisione accodata alloca nuovi numeri di oggetto (o riutilizza quelli esistenti per gli oggetti che sostituisce) rispetto allo stesso ObjectRegistry, così la numerazione degli oggetti resta coerente tra le revisioni. La nuova sezione cross-reference elenca solo gli oggetti toccati da questa revisione. Il nuovo trailer ripete le voci del trailer precedente e aggiunge /Prev che punta alla sezione precedente. È questa catena che un lettore percorre.
Il caso in cui questo conta in modo più evidente è la firma. Il ByteRangeCalculator di NextPDF (src/Security/Signature/ByteRangeCalculator.php) calcola l’array /ByteRange come due segmenti: tutto ciò che precede il valore della firma e tutto ciò che lo segue, così la firma copre l’intera revisione eccetto i propri byte. Poiché una modifica successiva viene accodata anziché scritta sopra quei byte, quell’intervallo non si sposta mai.
- Write base revision Header, body, xref section, trailer — the original bytes.
- Sign A /ByteRange digest covers the whole revision except the signature value itself.
- Edit and save Changed objects + a new xref section are appended; originals are untouched.
- New trailer chains back The appended trailer carries /Prev = offset of the previous xref section.
- Verify The first signature still covers the same unchanged bytes; the chain shows what came after.
Cosa dicono le evidenze
Sezione intitolata “Cosa dicono le evidenze”La regola append-only è normativa. Spec: ISO 32000-2, §7.5.6 ISO 32000-2 §7.5.6 stabilisce che il contenuto di un PDF può essere aggiornato in modo incrementale senza riscrivere l’intero file e che, in tal caso, le modifiche devono essere accodate alla fine del file, lasciando intatto il contenuto originale. Evidence: Standard-backed
La stessa clausola definisce il meccanismo. Una sezione cross-reference per un aggiornamento incrementale contiene voci solo per gli oggetti che sono stati modificati, sostituiti o eliminati. Gli oggetti eliminati restano nel file, ma vengono contrassegnati come eliminati tramite le rispettive voci cross-reference. Il trailer aggiunto deve contenere una voce /Prev che indica la posizione della sezione cross-reference precedente. Per un oggetto modificato, la voce dell’aggiornamento contiene l’offset di byte della nuova copia e prevale sul vecchio offset. Un lettore costruisce le proprie informazioni cross-reference in modo da accedere alla copia più recente di ciascun oggetto.
La conseguenza per la firma è indicata direttamente da
Spec: ISO 32000-2, §12.8.1 ISO 32000-2 §12.8.1 : un digest sull’intervallo di byte
viene calcolato su un intervallo del file, normalmente sull’intero file, escludendo
il valore della firma (la voce /Contents). Lo standard osserva poi che
se un documento firmato viene modificato e salvato tramite aggiornamento incrementale, i dati
corrispondenti all’intervallo di byte della firma originale vengono preservati, quindi se
la firma è valida lo stato del documento al momento della firma può essere
ricreato. L’append-only non è un dettaglio accessorio. È la proprietà da cui dipende il modello
di firma.
Esempio pratico
Sezione intitolata “Esempio pratico”Un PDF firmato e poi modificato, visto nella sua struttura. La revisione originale termina al proprio %%EOF. La seconda revisione viene accodata sotto di essa.
%PDF-2.0... original objects, including the signature dictionary ...xref0 8... entries for the original revision ...trailer<< /Size 8 /Root 1 0 R >>startxref920%%EOF <-- end of revision 1: the signed bytes stop here9 0 obj <-- revision 2, appended<< /Type /Annot /Subtype /Text /Contents (added after signing) >>endobjxref0 19 0 obj-entry...8 90000001740 00000 ntrailer<< /Size 10 /Root 1 0 R /Prev 920 >>startxref1980%%EOFUn validatore legge l’ultimo trailer, vede /Prev 920 e dispone così dell’intera catena. Può verificare la firma rispetto ai byte fino al primo %%EOF, che sono invariati. Può quindi segnalare separatamente che la revisione 2 ha aggiunto un’annotazione. La cronologia è contenuta nel file. Nulla è stato nascosto tramite sovrascrittura.
Equivoco comune
Sezione intitolata “Equivoco comune”La trappola è «un aggiornamento incrementale significa che la modifica è piccola, quindi è innocua». L’accodamento riguarda la conservazione dei byte, non la dimensione. Un aggiornamento incrementale può aggiungere una grande quantità di contenuto. Ciò che lo rende un aggiornamento incrementale è il fatto che non tocca i byte già presenti. Anche il corollario trae spesso in inganno: uno strumento che «ottimizza» o «linearizza» un PDF firmato riscrivendolo da zero produrrà un file più piccolo e ordinato e una firma invalidata, perché l’intervallo di byte firmato non esiste più. Salvare un PDF firmato in modalità incrementale e riscriverlo da zero non sono la stessa operazione.
Limiti e confini
Sezione intitolata “Limiti e confini”L’append-only protegge i byte. Di per sé non indica se le modifiche accodate fossero autorizzate. Una seconda revisione può legittimamente aggiungere una seconda firma, oppure può aggiungere contenuto che il primo firmatario non aveva mai inteso autorizzare. Stabilire quale sia il caso è compito della validazione della firma e della politica di rilevamento delle modifiche (DocMDP). L’accodamento è la base tecnica che rende possibile quell’analisi, non l’analisi stessa.
Questa pagina, inoltre, non tratta come vengono calcolati e uniti i due intervalli di byte di una firma, né cosa verifica una validazione completa. Sono argomenti distinti. La garanzia descritta qui riguarda i file scritti e aggiornati da un writer conforme: un file le cui revisioni precedenti erano già malformate non diventa ben formato per il fatto di essere accodato.
Mini-FAQ
Sezione intitolata “Mini-FAQ”Come si fa a sapere quante revisioni ha un PDF? Contare i marcatori %%EOF e seguire la catena /Prev a partire dall’ultimo trailer. Ogni sezione cross-reference raggiunta corrisponde a una revisione salvata.
Eliminare un oggetto lo rimuove dal file? No. Un aggiornamento incrementale contrassegna l’oggetto come eliminato nella sua voce cross-reference, ma i byte dell’oggetto restano nelle revisioni precedenti. «Eliminato» significa «non referenziato dalla revisione corrente», non «cancellato».
Un aggiornamento incrementale può cambiare la versione del PDF? Sì, impostando la voce /Version nel catalogo della revisione accodata. L’intestazione resta come è stata scritta. La voce /Version del catalogo ha la precedenza quando indica una versione successiva.
Documentazione correlata
Sezione intitolata “Documentazione correlata”- Cos’è davvero un PDF — il modello a oggetti e l’unica sezione cross-reference estesa da un aggiornamento.
- Come si collocano le firme in un PDF — il meccanismo basato sull’intervallo di byte che gli aggiornamenti incrementali servono a proteggere.
- Validare correttamente una firma — che cosa verifica una validazione corretta attraverso la cronologia delle revisioni di un file.
Glossario
Sezione intitolata “Glossario”- Aggiornamento incrementale — salvare una modifica accodando gli oggetti modificati, una nuova sezione cross-reference e un nuovo trailer alla fine del file, senza alterare i byte già esistenti.
/Prev— la voce del trailer (o del cross-reference stream) che contiene l’offset di byte della sezione cross-reference precedente. Collega le revisioni in una catena all’indietro.- Revisione — lo stato del file rappresentato da una sezione cross-reference e dal suo trailer. Un file con N sezioni cross-reference ha N revisioni.
/ByteRange— l’array in un dizionario di firma che indica i due segmenti di byte coperti dal digest della firma (tutto eccetto lo stesso valore della firma).- Intervallo di byte firmato — i byte esatti su cui è stato calcolato il digest di una firma. Gli aggiornamenti incrementali servono a far sì che questi byte non vengano mai spostati o sovrascritti.