Teknisk artikkel

Klassifisere hva som endret seg i en PDF etter signering

En signatur over en PDF forbyr ikke senere endringer. Den fikser et byteområde, og en inkrementell oppdatering appender nye byte etter den, så signaturen forblir matematisk gyldig mens dokumentet får nytt innhold. Om det innholdet er akseptabelt, er et politikks spørsmål, og DocMDP er der forfatteren slår fast politikken: ingen endringer i det hele tatt, bare skjemautfylling og signering, eller de pluss annotasjoner. Å håndheve den betyr å klassifisere hva som faktisk endret seg, noe som er det AnalyzeModifications gjør. Pek den mot en tidligere revisjon, og les deretter GetModificationLevel for den overordnede dommen og per-funn-aksessorene for nivået, objektnummeret og beskrivelsen av hver forskjell

Med det på plass kollapser DocMDP-håndhevelse til en sammenligning: er det beregnede nivået på eller under nivået politikken tillater

Diagram over PDFlibPas endringsnivåstige fra mlNone til mlUnclassified som viser DocMDP-policyhåndhevelse som én sammenligning i Delphi
TPLModificationLevel-stigen går fra mlNone til mlUnclassified, og DocMDP-håndhevelse reduseres til å sammenligne det beregnede nivået mot politikken

Hvorfor en signert PDF er forventet å endre seg

Tre legitime tilfeller, og de dekker det meste av hva du vil se. En annen signatar legger til sin signatur. En mottaker fyller ut skjemafeltene forfatteren lot stå åpne. Og langtidsvalideringsmateriale appendes: OCSP-responser og CRL-er skrevet inn i dokumentets sikkerhetslager slik at signaturen forblir verifiserbar etter at responserne er borte. Det siste er ikke bare tillatt, det er hva et veldrevet arkiv gjør med signerte dokumenter med vilje

Så «filen vokste etter signering» bærer ingen informasjon. Spørsmålet er alltid hva som ble lagt til, og svaret må komme fra å sammenligne dokumenttilstander snarere enn fra å overvåke byte. Append-mekanikken selv er dekket i artikkelen om inkrementelle oppdateringer

Klassifiser etter objektets form, ikke etter veien som produserte det

Klassifikatoren ser på hva et objekt er etter endringen, ikke på hvilket bibliotekkall som skapte det. Det er bevisst, fordi analysen kjøres mot filer produsert av annen programvare, der ingen kallvei er tilgjengelig å inspisere

Fire former gjenkjennes. Dokumentets sikkerhetslager og valideringsrelaterte informasjonsordbøker, kryssreferansestrømobjekter, katalogens metadataoppføring og signaturordbøker som bærer et byteområde, er langtidsarkivmateriale. Et objekt som bærer både en felttype og en feltverdi er skjemautfylling. Et objekt hvis type er annotasjon, eller hvis subtyper er én av de oppførte i ISO 32000-2 Tabell 168, er en annotasjonsendring. Alt annet er uklassifisert

Beslutningstreet PDFlibPas anvender på hvert endret PDF-objekt, som sorterer oppdateringer inn i arkiv-, skjemautfyllings-, annotasjons- eller uklassifiserte nivåer
Hvert endret objekt klassifiseres etter hva det er — sikkerhetslager, xref-strøm, felt, annotasjon — aldri etter kallet som produserte det

Fjerninger behandles strengere enn tillegginger. Et fjernet objekt hvitelistes bare når objektet på den gamle siden selv var arkivmateriale, noe som dekker det normale tilfellet der et sikkerhetslager erstattes av et nyere. Enhver annen fjerning er uklassifisert, for å slette innhold fra et signert dokument er ikke noe et tillatelsesnivå autoriserer. Dokumentnivåforskjeller er strengere ennå: en endring i sideantall går rett til uklassifisert uten å undersøke individuelle objekter, siden intet DocMDP-nivå tillater å legge til eller fjerne sider

Hvitelisten heller mot å avslå

Dette er designregelen som styrer enhver grensetilfellebeslutning. En endring feilaktig klassifisert som tillatt, er en signatur som validerer over innhold forfatteren aldri autoriserte. En endring feilaktig klassifisert som uklassifisert, er et dokument som blir flagget og gjennomgått av et menneske. De to feilene er ikke symmetriske, så hvitelisten forblir smal og ugjenkjente former faller gjennom til uklassifisert i stedet for å gjettes

Det har en praktisk konsekvens verdt å forutse: filer fra uvanlige produsenter vil noen ganger rapportere uklassifiserte endringer som ved inspeksjon er ufarlige. Det riktige svaret er å se på funndetaljen og objektnummeret snarere enn å utvide hvitelisten, fordi en hviteliste som vokser for å dempe enkeltrapporter slutter å være en sikkerhetskontroll

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 ordnet mlNone, mlLTAUpdates, mlFormFilling,
    // mlAnnotations, mlUnclassified; getteren returnerer sin ordinal
    Level := Pdf.GetModificationLevel;
    // DocMDP-håndhevelse er nå én sammenligning mot politikken
    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;

Det overordnede nivået er maksimum over alle funn, noe som er den eneste forsvarlige aggregeringen: et dokument som inneholder nittini arkivtillegginger og én uklassifisert endring, er en uklassifisert endring

Under panseret: fingeravtrykk, ikke kryptografiske hash

Sammenligningsmotoren som CompareWith eksponerer, og som endringsanalysen er bygget på, identifiserer objekter ved et fingeravtrykk av deres normaliserte kropp ved hjelp av en ikke-kryptografisk 64-bits hash snarere enn SHA-256. Det er et gjennomtenkt valg. Det strukturell sammenligning trenger, er determinisme: den samme objektkroppen må alltid produsere det samme fingeravtrykket innenfor én kjøring. Den trenger ikke kollisjonsmotstand, for en angriper som kontrollerer begge sider av sammenligningen, har allerede vunnet med andre midler, og å betale for en full kryptografisk hash over hvert objekt i et dokument med en million objekter er en reell kostnad uten gevinst

To normaliseringsregler betyr mer enn hash-valget. Indirekte referanser foldes til en plassholdertoken i stedet for å ekspanderes inn i det refererte innholdet: å ekspandere ville kopiere et delt objekts kropp inn i hver referer, så én liten redigering av en delt fontbeskriver ville ugyldiggjøre fingeravtrykket til hvert objekt som når det, og rapporten ville være uleselig. Og objektnumrene selv er ekskludert fra fingeravtrykket, fordi en omskriving kan nummerere objekter på nytt uten å endre noe semantisk

Matching kjører deretter i to passeringer, justert etter fingeravtrykk først og paring av resten etter objektnummer for å identifisere endringer snarere enn en tillegging pluss en fjerning. Billige sjekker kommer først gjennomgående: en sideantallsforskjell rapporteres før noen objekttraversering begynner

Topasseringers PDF-revisjonsdiff i PDFlibPas: sideantallssjekk først, 64-bits fingeravtrykk, fingeravtrykksjustering deretter objektnummerparing
Sammenligningsmotoren fingeravtrykker normaliserte objektkropper, rapporterer sideantallsforskjeller først, og matcher deretter etter fingeravtrykk og objektnummer

En felle: selv-sammenligning er ikke garantert identisk

Den naturlige første testen for en diff-motor er å sammenligne en fil mot seg selv og påstå at resultatet er identisk. Den påstanden holder ikke her, og grunnen er lærerik. Den offentlige lasteveien og den lavernivå dokumentlasteveien konfigurerer ikke dekoding identisk, så den samme filen lastet gjennom de to rutene kan produsere fingeravtrykk som er forskjellige for noen objekter. Motoren tar ikke feil; de to lastene produserte genuint forskjellige minnetilstander

I stedet for å tvinge de to veiene sammen, er sammenligningssemantikken formulert smalt: analysen sammenligner den nåværende dokumenttilstanden mot en tidligere revisjon, og rapporterer identisk bare når de to fingeravtrykksmengdene sammenfaller eksakt. Det er spørsmålet brukere faktisk stiller, og det krever ikke at de to lasterne er utskiftbare. Når du designer en sammenligningsfunksjon, er det å definere hva «det samme» betyr, mer av arbeidet enn å beregne det

Hvor du bruker det

To steder. I en valideringsrapport, ved siden av signatursjekking, slik at en gransker ser ikke bare om signaturen er kryptografisk intakt, men hva som skjedde med dokumentet etterpå; signatursiden er dekket i PAdES-signering og -validering. Og i en inntakssport, der et dokument som ankommer utenfra sjekkes mot kopien du sendte, slik at en returnert kontrakt med en tillagt annotasjon behandles annerledes enn én med en redigert side

Én advarsel om omfang. Denne analysen forteller deg hva som endret seg mellom to revisjoner av den samme dokumentlinjen. Den forteller deg ikke om synlig innhold er villedende, om en skjemafelts utseendestrøm matcher sin verdi, eller om tekst skjult under et overlegg fortsatt er til stede i innholdsstrømmen. Det trenger separat behandling, og innholdsfjerningssiden av det er dekket i artikkelen om ekte sverting. Analysens og sammenligningens inngangspunkter er dokumentert på produktsiden for losLab PDF Developer Library