Teknisk artikel

Klassificera vad som ändrats i en PDF efter signering

En signatur över en PDF förbjuder inte senare ändringar. Den fixerar ett byteintervall, och en inkrementell uppdatering lägger till nya byte efter den, så signaturen förblir matematiskt giltig medan dokumentet får nytt innehåll. Huruvida det innehållet är acceptabelt är en policyfråga, och DocMDP är där upphovspersonen anger policyn: inga ändringar alls, enbart formulärifyllning och signering, eller det plus anteckningar. Att tillämpa den betyder att klassificera vad som faktiskt ändrats, vilket är vad AnalyzeModifications gör. Rikta den mot en tidigare revision och läs sedan GetModificationLevel för den övergripande bedömningen och de per-fynd-accessorer som ger nivå, objektnummer och beskrivning för varje skillnad

Med det på plats kollapsar DocMDP-tillämpningen till en jämförelse: är den beräknade nivån på eller under den nivå policyn tillåter

Diagram över PDFlibPas ändringsnivåstege från mlNone till mlUnclassified som visar DocMDP-policytutvingning som en jämförelse i Delphi
TPLModificationLevel-stegen löper från mlNone till mlUnclassified, och DocMDP-tillämpningen reduceras till att jämföra den beräknade nivån mot policyn

Varför en signerad PDF förväntas ändras

Tre legitima fall, och de täcker det mesta du kommer att se. En andra undertecknare lägger till sin signatur. En mottagare fyller i formulärfält som upphovspersonen lämnat öppna. Och material för långtidsvalidering läggs till: OCSP-svar och CRL:er skrivna in i dokumentets security store så att signaturen förblir verifierbar när respondrarna är borta. Det sista är inte bara tillåtet, det är vad ett välhanterat arkiv gör med signerade dokument med flit

Så ”filen växte efter signering” bär ingen information. Frågan är alltid vad som lades till, och svaret måste komma från att jämföra dokumenttillstånd i stället för från att iaktta byte. Appendmekaniken själv tas upp i artikeln om inkrementella uppdateringar

Klassificera efter objektets form, inte efter vägen som skapade det

Klassificeraren tittar på vad ett objekt är efter ändringen, inte på vilket biblioteksanrop som skapade det. Det är medvetet, eftersom analysen körs mot filer producerade av annan programvara, där ingen anropsväg finns att inspektera

Fyra former känns igen. Information dictionaries för document security store och valideringsrelaterad information, cross-reference-strömobjekt, katalogens metadatapost och signaturordlistor med ett byteintervall är material för långtidsarkiv. Ett objekt som bär både en fälttyp och ett fältvärde är formulärifyllning. Ett objekt vars typ är annotation, eller vars subtype är en av dem som listas i ISO 32000-2 tabell 168, är en anteckningsändring. Allt annat är oklassificerat

Beslutsträd som PDFlibPas tillämpar på varje ändrat PDF-objekt och som sorterar uppdateringar i arkiv-, formulärifyllnings-, antecknings- eller oklassificerade nivåer
Varje ändrat objekt klassificeras efter vad det är — security store, xref-ström, fält, anteckning — aldrig efter anropet som skapade det

Borttagningar behandlas strängare än tillägg. Ett borttaget objekt vitlistas bara när objektet på den gamla sidan själv var arkivmaterial, vilket täcker det normala fallet att en security store ersätts av en nyare. Varje annan borttagning är oklassificerad, för att ta bort innehåll ur ett signerat dokument är inget som en behörighetsnivå auktoriserar. Skillnader på dokumentnivå är ännu strängare: en ändring i sidantal går direkt till oklassificerad utan att enskilda objekt granskas, eftersom ingen DocMDP-nivå tillåter att sidor läggs till eller tas bort

Vitlistan tenderar att vägra

Det är designregeln som styr varje gränsfall. En ändring som felaktigt klassificeras som tillåten är en signatur som validerar över innehåll upphovspersonen aldrig auktoriserade. En ändring som felaktigt klassificeras som oklassificerad är ett dokument som flaggas och granskas av en människa. Dessa två fel är inte symmetriska, så vitlistan hålls smal och okända former faller igenom till oklassificerad i stället för att gissas fram

Det har en praktisk konsekvens värd att förutse: filer från ovanliga producenter kommer ibland att rapportera oklassificerade ändringar som, vid granskning, är harmlösa. Det rätta svaret är att titta på fynddetaljen och objektnumret i stället för att bredda vitlistan, för en vitlista som växer för att tysta enskilda rapporter slutar vara en säkerhetskontroll

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 ordnad mlNone, mlLTAUpdates, mlFormFilling,
    // mlAnnotations, mlUnclassified; gettern returnerar dess ordinal
    Level := Pdf.GetModificationLevel;
    // DocMDP-tillämpning är nu en enda jämförelse mot policyn
    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;

Den övergripande nivån är maximum över alla fynd, vilket är den enda försvarbara aggregeringen: ett dokument som innehåller nittinio arkivtillägg och en oklassificerad ändring är en oklassificerad ändring

Under huven: fingeravtryck, inte kryptografiska hashar

Jämförelsemotorn som CompareWith exponerar, och som ändringsanalysen är byggd på, identifierar objekt med ett fingeravtryck av sin normaliserade kropp med en icke-kryptografisk 64-bit hash i stället för SHA-256. Det är ett genomtänkt val. Vad strukturell jämförelse behöver är determinism: samma objektkropp måste alltid ge samma fingeravtryck inom en körning. Den behöver inte kollisionsmotstånd, för en angripare som kontrollerar båda sidorna av jämförelsen har redan vunnit med andra medel, och att betala för en fullständig kryptografisk hash över varje objekt i ett dokument med en miljon objekt är en verklig kostnad utan nytta

Två normaliseringsregler betyder mer än hashvalet. Indirekta referenser viks till en platshållartoken i stället för att expanderas till det refererade innehållet: expansion skulle kopiera ett delat objekts kropp till varje refererare, så en liten redigering av en delad teckensnittsbeskrivare skulle ogiltigförklara fingeravtrycket för varje objekt som når den, och rapporten skulle vara oläsbar. Och objektnumren själva exkluderas från fingeravtrycket, eftersom en omskrivning kan omnumrera objekt utan att ändra något semantiskt

Matchningen körs sedan i två pass, först inriktning efter fingeravtryck och sedan parning av resten efter objektnummer för att identifiera ändringar i stället för ett tillägg plus en borttagning. Billiga kontroller kommer först genomgående: en skillnad i sidantal rapporteras innan någon objektgenomgång börjar

Tvåpass-revisionsdiff i PDFlibPas: sidantalskontroll först, 64-bit fingeravtryck, fingeravtrycksinriktning och sedan parning efter objektnummer
Jämförelsemotorn fingeravtrycker normaliserade objektkroppar, rapporterar skillnader i sidantal först och matchar sedan efter fingeravtryck och objektnummer

En fälla: självjämförelse är inte garanterat identisk

Det naturliga första testet för en diffmotor är att jämföra en fil med sig själv och hävda att resultatet är identiskt. Det antagandet håller inte här, och orsaken är lärorik. Det publika inläsningsspåret och den lägre nivåns dokumentinläsningsspår konfigurerar inte avkodningen identiskt, så samma fil inläst genom de två vägarna kan ge fingeravtryck som skiljer sig för vissa objekt. Motorn är inte fel; de två inläsningarna producerade genuint olika tillstånd i minnet

I stället för att tvinga ihop de två spåren anges jämförelsesemantiken smalt: analysen jämför dokumentets aktuella tillstånd mot en tidigare revision och rapporterar identisk bara när de två fingeravtrycksuppsättningarna sammanfaller exakt. Det är frågan användare faktiskt ställer, och den kräver inte att de två inläsarna är utbytbara. När du designar en jämförelsefunktion är att definiera vad ”detsamma” betyder mer av arbetet än att beräkna det

Var man använder det

Två platser. I en valideringsrapport, jämte signaturkontroll, så att en granskare ser inte bara om signaturen är kryptografiskt intakt utan vad som hände med dokumentet därefter; signatursidan tas upp i PAdES-signering och validering. Och i en intagsgrind, där ett dokument som anländer utifrån kontrolleras mot kopian du skickade, så att ett returnerat avtal med en tillagd anteckning behandlas annorlunda än ett med en redigerad sida

En brasklapp om omfattning. Denna analys talar om för dig vad som ändrats mellan två revisioner av samma dokumentlinje. Den talar inte om för dig huruvida synligt innehåll är missvisande, huruvida en formulärfälts appearance-ström stämmer med sitt värde eller huruvida text gömd under ett overlay fortfarande finns i innehållsströmmen. Det behöver separat behandling, och innehållsborttagningssidan tas upp i artikeln om äkta svärtning. Analys- och jämförelseingångspunkterna är dokumenterade på produktsidan för losLab PDF Developer Library