Tehnični članak

DocMDP in FieldMDP: revizija PDF revizij v Delphiju

Podpisan PDF, ki se je po podpisu spremenil, ni samodejno pokvarjen. ISO 32000-1 dovoljuje postopne posodobitve na vrhu podpisa, le nekatere od njih pa kršijo politiko, ki jo je določil podpisnik. HotPDF Component za Delphi in C++Builder na to vprašanje odgovori z AnalyzeLoadedSignatureRevisions, ki razvrsti vsako revizijo po podpisu in jo oceni glede na DocMDP in FieldMDP. Scenarij je znan vsakomur, ki dostavlja programsko opremo za pogodbe: vaša stranka podpiše kupoprodajno pogodbo, jo pošlje naprej in jo dobi nazaj s pripeto stranjo aneksa. Bralnik prikaže rumen trak, ki pravi, da je podpis nedotaknjen, dokument pa je bil od podpisa spremenjen, nihče v sobi pa ne more povedati, ali gre za normalen delovni tok soopodpisa ali za nekoga, ki tiho ureja podpisano pogodbo

Kaj šteje za zakonito spremembo po podpisu?

Sprememba je zakonita, kadar njena semantična kategorija sodi znotraj dovoljenja, ki ga je izjavil certificirajoč podpis. ISO 32000-1 §12.8.2.2 opredeljuje transformacijo DocMDP z vrednostjo /P 1, 2 ali 3: 1 ne dovoljuje nobenih sprememb, 2 dovoljuje izpolnjevanje obrazcev in podpisovanje, 3 dovoljuje izpolnjevanje obrazcev, podpisovanje in opombe. HotPDF te izpostavi kot vrednosti THPDFDocMDPPermission dmpNoChanges, dmpFormFillAndSign in dmpFormFillSignAndAnnotate, pri čemer je dmpNone pridržan za rezultate pregleda, ki sploh ne nosijo transformacije DocMDP

Kategorije so urejene, ta ureditev pa je motor celotnega preverjanja. THPDFRevisionModificationLevel teče rmlNone, rmlLongTermValidation, rmlFormFillAndSign, rmlAnnotations, rmlOther, namerno razporejene tako, da večja ordinalna vrednost nikoli ni manj omejevalna. Celoten dokument se skrči na najvišjo raven, opaženo med vsemi revizijami po podpisu, primerjava DocMDP pa postane preprost test celih števil. Ena nianса je pomembna zgodaj: pri dmpNoChanges analiza še vedno sprejme rmlLongTermValidation. Dodajanje validacijskega gradiva DSS in VRI ali časovnega žiga dokumenta k certificirani datoteki je vzdrževanje podpisa, ne sprememba dokumenta, obravnavanje tega kot kršitve pa bi pokvarilo vsak obstoječi delovni tok dolgoročnega arhiviranja

Kako HotPDF obnovi verigo revizij?

Strukturno, ne hevristično. Po ISO 32000-1 §7.5.6 postopna posodobitev doda nov odsek navzkrižnih referenc, čigar /Prev kaže na prejšnjega, zato HotPDF prebere startxref z repa, razčleni tamkajšnji odsek, sledi /Prev nazaj in ponovi postopek, pri čemer vrne odseke od najstarejšega naprej. V tej zanki sta dve varnostni omejitvi, obe pa je vredno poznati pri triaži datoteke, ki odpove: /Prev, ki kaže na že obiskan odmik, sprehod konča z izrecno diagnostiko cikla namesto vrtenja, veriga, daljša od tisoč revizij, pa je povsem zavrnjena. Obe se pokažeta v Analysis.Issue, funkcija pa vrne False, nobene od njiju pa ne gre prekriti, saj je ciklični /Prev nepravilna ali sovražna datoteka, ne le nenavadna

V resničnih dokumentih se pojavijo štiri zgodovinske oblike in vse štiri so obravnavane: tradicionalne tabele xref, razčlenjene vrstico za vrstico, tokovi navzkrižnih referenc, dekompresirani in dekodirani prek svojih polj /W in /Index, hibridne referenčne datoteke, katerih tradicionalna sled nosi ključ /XRefStm, ki se razčleni in združi v isto revizijo (primer producenta Office, obravnavan v članku o hibridnih tokovih navzkrižnih referenc), in objekti, ki živijo znotraj vsebnika ObjStm, ki so pomembni, ker sodobna posodobitev običajno postavi spremenjen slovar v stisnjen tok namesto neposrednega zapisa, kot je opisano v članku o tokovih objektov in postopnih posodobitvah. Podpis zasidra delitev: /ByteRange[2] + /ByteRange[3] postane SignedRevisionLength, vsak odsek na tem odmiku ali za njim pa je po podpisu. Ali se razpon bajtov še vedno pravilno zgosti, je ločeno vprašanje, na katerega odgovori VerifyLoadedSignature, obravnavano v članku o preverjanju digitalnih podpisov PDF

Kako se razvrsti vsak spremenjen objekt

Razvrščanje teče po objektih, nato pa se širi po referencah. Za vsako številko objekta, ki jo odsek po podpisu prizadene, HotPDF prebere novo telo in telo, kakršno je bilo v podpisanem posnetku; enako telo je rmlNone, saj producenti res prepisujejo objekte, ne da bi jih spremenili. Prepoznavalniki so namerno ozki. Objekt /Type /DocTimeStamp ali tak, čigar /SubFilter je ETSI.RFC3161, je rmlLongTermValidation, prav tako vse, kar je dosegljivo iz drevesa /DSS kataloga; slovar /Type /Sig je rmlFormFillAndSign. Za vsebnike je test, kateri ključi so se premaknili, ne kaj objekt je: katalog lahko le pridobi ali spremeni /DSS, /Extensions ali /AcroForm; slovar AcroForm le /Fields, /SigFlags, /NeedAppearances, /DR, /DA ali /Q; stran le /Annots; polje ali gradnik le /V, /AP, /AS ali /M. Vse zunaj teh množic pade na rmlOther, kar je natanko način, kako se ujame pripeta stran aneksa: dodajanje strani preuredi drevo strani na načine, ki jih nobena bela lista ne pokriva, in nobena količina zakonitega izpolnjevanja obrazcev temu ni podobna

Nato se ravni širijo, pri čemer vsak vsebnik podeduje najvišjo raven spremenjenih otrok, na katere kaže, ponavljajoč se, dokler se dodelitev ne ustali. To je tisto, kar omogoča delovanje tokov videza. Izpolnjeno besedilno polje prepiše /V in kaže na svež tok /AP, ta tok pa je sam po sebi anonimna gmota vsebinskih operatorjev brez tipa, ki bi ga bilo mogoče prepoznati; ker je polje, ki ga last, rmlFormFillAndSign, tok podeduje isto raven namesto da bi padel na rmlOther. Isto širjenje prenese kontekst DSS na tokove potrdil in preklicev, ki bi bili sicer nerazvrstljivi

Zakaj neberljiv objekt šteje za kršitev?

Ker je alternativa validator, ki ga premaga pisanje nečesa, česar ne razume. Tri situacije se v HotPDF končajo pri rmlOther brez pritožbe: objekt, čigar telesa ni bilo mogoče prebrati iz revizije, objekt, ki ga revizija označi kot sproščenega, in objekt, ki se ne ujema z nobenim od zgornjih prepoznavalnikov. Vsak zabeleži specifično diagnostiko v polje Issue revizije, tako da operater vidi, katera številka objekta je pripeljala do verdikta

Sproščanje je najostrejše od treh. Revizija po podpisu, ki predhodno definiran objekt označi kot prost, je izbrisala vsebino iz podpisanega dokumenta, in nobena raven dovoljenja po §12.8.2.2 tega ne dovoljuje; številke objektov pristanejo v FreedObjectNumbers, revizija pa se dvigne na rmlOther. Neberljivi objekti sledijo isti logiki iz drugega razloga. Validator, ki objekta ne more razčleniti, nima podlage, da bi ga razglasil za neškodljivega, iskren odgovor na to pa ni molk. Poročanje o nenavadni, a nedolžni konstrukciji kot kršitvi stane pregled človeka; nasprotna napaka pa dostavi podpisano pogodbo z neopaženim urejanjem v njej

Branje verdikta v Delphiju

Klic je kratek. Naložite dokument, izberite indeks podpisa, preberite zapis; preobtežitev brez parametrov ponovno odpre datoteko, iz katere je bil dokument naložen, preobtežitev TStream pa vzame bajte, ki jih poda klicatelj, in pred vrnitvijo obnovi položaj toka. PolicyCompliant je edini logični podatek, ki ga večina klicateljev želi, saj združuje tri neodvisne odločitve: strukturno veljavnost slovarjev dovoljenj, DocMDPCompliant in FieldMDPCompliant. Sestavne dele v svojem vmesniku raje ohranite vidne, kot da bi jih zložili skupaj, in upoštevajte, da dokument brez transformacije DocMDP pusti DocMDPCompliant na True, saj navaden odobritveni podpis ne izjavi nobene politike, ki bi jo bilo mogoče kršiti, skupni ModificationLevel pa je nato opisen, ne verdikt

var
  Pdf: THotPDF;
  Analysis: THPDFSignatureRevisionAnalysis;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('contract-countersigned.pdf') > 0 then
    begin
      if Pdf.AnalyzeLoadedSignatureRevisions(0, Analysis) then
      begin
        if Analysis.PolicyCompliant then
          Writeln('Post-signature changes stay inside the signing policy')
        else
          Writeln('Policy violation: ', string(Analysis.Issue));
      end
      else
        Writeln('Analysis could not run: ', string(Analysis.Issue));
    end;
  finally
    Pdf.Free;
  end;
end;

Za triažo običajno želite razčlenitev po revizijah namesto povzetka, ker pove, kdaj v zgodovini dokumenta je šlo kaj narobe. Vsak vnos v Analysis.Revisions nosi svoj indeks v verigi, odmik navzkrižne reference, na katerem je bil zapisan, svojo raven spremembe in vpletene številke objektov

const
  LevelNames: array[THPDFRevisionModificationLevel] of string =
    ('none', 'long-term validation', 'form fill and sign',
     'annotations', 'other');
var
  I: Integer;
begin
  Writeln(Format('%d revisions in chain, signature sits at index %d',
    [Analysis.TotalRevisionCount, Analysis.SignedRevisionIndex]));
  for I := 0 to High(Analysis.Revisions) do
    Writeln(Format('  rev %d at offset %d: %s (%d changed, %d freed) %s',
      [Analysis.Revisions[I].RevisionIndex,
       Analysis.Revisions[I].XRefOffset,
       LevelNames[Analysis.Revisions[I].ModificationLevel],
       Length(Analysis.Revisions[I].ChangedObjectNumbers),
       Length(Analysis.Revisions[I].FreedObjectNumbers),
       string(Analysis.Revisions[I].Issue)]));
end;

FieldMDP se presoja ločeno, in to je namerno

Dokument lahko izpolnjuje DocMDP in je vseeno nezakonit, zato je FieldMDPCompliant ločena logična vrednost, ne zložena v primerjavo ravni. ISO 32000-1 §12.8.2.4 opredeljuje transformacijo FieldMDP, §12.7.5.5 pa povezan vnos /SigFieldLock, da zamrzneta poimenovana polja obrazca v trenutku podpisovanja, tudi kadar dokument kot celota še vedno dovoljuje izpolnjevanje obrazcev. Izpolnjevanje polja je dejanje ravni 2; izpolnjevanje polja, ki ga je podpisnik zaklenil, pa je kršitev ne glede na raven. HotPDF prebere obseg v THPDFFieldLockAction kot flaAll, flaInclude ali flaExclude, pri čemer je flaNone za rezultate brez politike zaklepanja, imena pa v Permissions.FieldNames: flaAll zaklene vse, flaInclude zaklene naštete imena, flaExclude zaklene vse razen njih. Ena podrobnost je pomembna pri branju rezultatov, namreč da se v ChangedFieldNames poročajo le polja, ki so bila že prisotna v podpisanem posnetku, saj polje, ustvarjeno v celoti po podpisu, nima podpisanega stanja, ki bi mu nasprotovalo, in ga namesto tega ujame pot DocMDP

var
  Source: TFileStream;
  Analysis: THPDFSignatureRevisionAnalysis;
  I: Integer;
begin
  Source := TFileStream.Create('contract.pdf', fmOpenRead or fmShareDenyWrite);
  try
    if Pdf.AnalyzeLoadedSignatureRevisions(0, Source, Analysis) then
      if Analysis.Permissions.HasFieldMDP and (not Analysis.FieldMDPCompliant) then
        for I := 0 to High(Analysis.ChangedFieldNames) do
          Writeln('modified after locking: ',
            string(Analysis.ChangedFieldNames[I]));
  finally
    Source.Free;  // stream position was restored before the call returned
  end;
end;

Kaj vam ta analiza ne pove

Ne preveri podpisa. AnalyzeLoadedSignatureRevisions sklepa o strukturi in dovoljenjih; ali se podpisan razpon bajtov še vedno zgosti do vrednosti v bloku CMS in ali se veriga potrdila podpisnika povezuje z nečim, kar zaupate, odgovarjata VerifyLoadedSignature in VerifyLoadedSignatureWithTrust. Datoteka je lahko popolnoma skladna s politiko in kriptografsko brez vrednosti, zato oba pregleda spadata drug ob drugem v vsako pravo vstopno vrata za sprejem. Prav tako ne bere namena znotraj vsebinskih tokov: stran, katere vsebinski tok je bil v celoti zamenjan, se ujame kot sprememba zunaj bele liste, analiza pa vam ne pove, da je zamenjava zamenjala znesek plačila. Verdikt rmlOther pomeni, da naj pogleda človek, ne da se je zgodila prevara, skladen verdikt pa pomeni, da sprememba sodi v dovoljeno kategorijo, ne da je bila sprememba želena. Kadar potrebujete le tisto, kar je podpisnik izjavil, brez sprehoda po revizijah, GetLoadedSignaturePermissions sam po sebi vrne slovarje politike

Vse tukaj opisano teče izvorno v Delphiju in C++Builderju brez zunanje storitve za podpisovanje v verigi, zaradi česar je praktično to poganjati na vsakem dohodnem dokumentu, ne le na tistih, za katere je nekdo že sumil. Celoten API za podpise in revizije, vključno z metodami za dovoljenja in preverjanje, s katerimi se pari, je del HotPDF Component za Delphi in C++Builder