Tehnički članak

DocMDP i FieldMDP: revizija PDF revizija u Delphi

Potpisan PDF koji se promenio posle potpisivanja nije automatski pokvaren. ISO 32000-1 dozvoljava inkrementalna ažuriranja preko potpisa, i samo neka od njih krše politiku koju je potpisnik postavio. HotPDF Component za Delphi i C++Builder odgovara na to pitanje pomoću AnalyzeLoadedSignatureRevisions, koji klasifikuje svaku reviziju posle potpisa i ocenjuje je naspram DocMDP i FieldMDP. Scenario je poznat svakome ko isporučuje softver za ugovore: vaš klijent potpiše ugovor o kupovini, pošalje ga, i dobije ga nazad sa prikačenom stranicom aneksa. Čitač pokazuje žutu traku koja kaže da je potpis netaknut, ali da je dokument izmenjen otkad je potpisan, i niko u prostoriji ne može reći da li je to normalan tok kontrapotpisa ili neko ko tiho menja potpisan ugovor

Šta se računa kao legalna izmena posle potpisivanja?

Izmena je legalna kad njena semantička kategorija upada unutar dozvole koju je sertifikujući potpis deklarisao. ISO 32000-1 §12.8.2.2 definiše DocMDP transformaciju sa vrednošću /P 1, 2 ili 3: 1 ne dozvoljava nikakve izmene, 2 dozvoljava popunjavanje formulara i potpisivanje, 3 dozvoljava popunjavanje formulara, potpisivanje i anotacije. HotPDF izlaže ih kao vrednosti THPDFDocMDPPermission dmpNoChanges, dmpFormFillAndSign i dmpFormFillSignAndAnnotate, sa dmpNone rezervisanim za rezultate inspekcije koji ne nose nikakvu DocMDP transformaciju

Kategorije su uređene, i taj redosled je motor cele provere. THPDFRevisionModificationLevel ide rmlNone, rmlLongTermValidation, rmlFormFillAndSign, rmlAnnotations, rmlOther, namerno raspoređeni tako da veći ordinal nikad nije manje restriktivan. Ceo dokument se svodi na maksimalan nivo primećen kroz svaku reviziju posle potpisa, i DocMDP poređenje postaje jedan test celog broja. Jedna nijansa je bitna rano: na dmpNoChanges analiza i dalje prihvata rmlLongTermValidation. Dodavanje DSS i VRI materijala validacije ili vremenskog žiga dokumenta u sertifikovan fajl je održavanje potpisa, ne izmena dokumenta, i tretiranje toga kao prekršaja bi razbilo svaki postojeći tok dugoročnog arhiviranja

Kako HotPDF ponovo gradi lanac revizija?

Strukturno, ne heuristički. Po ISO 32000-1 §7.5.6, inkrementalno ažuriranje nadovezuje novi cross-reference odeljak čiji /Prev pokazuje na prethodni, tako da HotPDF čita startxref sa kraja, parsira odeljak tamo, prati /Prev unazad i ponavlja, vraćajući odeljke od najstarijeg do najnovijeg. Dva bezbednosna ograničenja sede u toj petlji i oba je vredno znati kad se trijažira fajl koji otkazuje: /Prev koji pokazuje na već posećen offset prekida obilazak sa eksplicitnom dijagnostikom ciklusa umesto da se vrti u krug, a lanac duži od hiljadu revizija se u potpunosti odbija. Oba se pojavljuju u Analysis.Issue sa funkcijom koja vraća False, i nijedno ne treba prekrivati, jer je ciklični /Prev deformisan ili neprijateljski fajl, a ne samo neobičan

Četiri istorijska oblika se pojavljuju u realnim dokumentima i sva četiri su obrađena: tradicionalne xref tabele parsirane red po red, cross-reference tokovi dekomprimovani i dekodovani kroz svoja polja /W i /Index, hibridni fajlovi čiji tradicionalni trailer nosi ključ /XRefStm koji se parsira i spaja u istu reviziju (slučaj Office producenta, pokriven u članku o hibridnim cross-reference tokovima), i objekti koji žive unutar kontejnera ObjStm, koji su bitni jer moderno ažuriranje obično stavlja izmenjen rečnik u komprimovan tok umesto da ga piše direktno, kako je opisano u tekstu o object streams i inkrementalnim ažuriranjima. Potpis usidruje podelu: /ByteRange[2] + /ByteRange[3] postaje SignedRevisionLength, i svaki odeljak na tom offsetu ili posle njega je posle-potpisa. Da li se opseg bajtova i dalje ispravno heš-uje je odvojeno pitanje, na koje odgovara VerifyLoadedSignature, pokriveno u članku o verifikaciji digitalnih PDF potpisa

Kako se svaki izmenjeni objekat klasifikuje

Klasifikacija se izvodi po objektu, a zatim se propagira duž referenci. Za svaki broj objekta koji odeljak posle potpisa dodirne, HotPDF čita novo telo i telo kakvo je stajalo u potpisanom snimku; identično telo je rmlNone, jer producenti zaista ponovo pišu objekte bez izmene. Prepoznavaoci su namerno uski. Objekat sa /Type /DocTimeStamp, ili onaj čiji je /SubFilter ETSI.RFC3161, je rmlLongTermValidation, kao i sve dostupno iz stabla /DSS kataloga; rečnik /Type /Sig je rmlFormFillAndSign. Za kontejnere test je koji ključevi su se pomerili, ne šta je objekat: katalog sme samo da dobije ili izmeni /DSS, /Extensions ili /AcroForm; AcroForm rečnik samo /Fields, /SigFlags, /NeedAppearances, /DR, /DA ili /Q; stranica samo /Annots; polje ili widget samo /V, /AP, /AS ili /M. Sve van tih skupova pada na rmlOther, što je tačno kako se prikačena stranica aneksa hvata: dodavanje stranice preuređuje stablo stranica na način koji nijedna bela lista ne pokriva, i nijedna količina legitimnog popunjavanja formulara tome ne liči

Zatim se nivoi propagiraju, pri čemu svaki kontejner nasleđuje maksimalan nivo izmenjene dece na koju pokazuje, iterirano dok dodela ne stabilizuje. To je ono što čini da tokovi izgleda rade. Popunjeno tekstualno polje ponovo piše /V i pokazuje na svež tok /AP, a taj tok je sam po sebi anonimna gomila operatora sadržaja bez tipa za prepoznavanje; pošto je polje koje ga poseduje rmlFormFillAndSign, tok nasleđuje isti nivo umesto da padne na rmlOther. Ista propagacija nosi DSS kontekst na tokove sertifikata i opoziva koji bi inače bili neklasifikovani

Zašto se nečitljiv objekat računa kao prekršaj?

Zato što je alternativa validator poražen pisanjem nečega što ne razume. Tri situacije se završavaju na rmlOther bez žalbe u HotPDF: objekat čije telo se nije moglo pročitati iz revizije, objekat koji revizija označava kao oslobođen, i objekat koji ne odgovara nijednom od gore navedenih prepoznavalaca. Svaki beleži specifičnu dijagnostiku u polju Issue revizije, tako da operater može videti koji je broj objekta proizveo verdikt

Oslobađanje je najoštrije od tri. Revizija posle potpisa koja označava prethodno definisan objekat kao slobodan je obrisala sadržaj iz potpisanog dokumenta, i nijedan nivo dozvole pod §12.8.2.2 to ne dozvoljava; brojevi objekata slete u FreedObjectNumbers i revizija se podiže na rmlOther. Nečitljivi objekti prate istu logiku iz drugačijeg razloga. Validator koji ne može da parsira objekat nema osnov da ga proglasi bezopasnim, a iskren odgovor na to nije ćutanje. Prijavljivanje neobične ali bezopasne konstrukcije kao prekršaja košta ljudsku reviziju; suprotna greška isporučuje potpisan ugovor sa neprimećenom izmenom unutra

Čitanje verdikta u Delphi

Poziv je kratak. Učitajte dokument, izaberite indeks potpisa, pročitajte zapis; preopterećenje bez parametara ponovo otvara fajl iz kog je dokument učitan, a preopterećenje TStream uzima bajtove koje pozivalac obezbedi i vraća poziciju toka pre povratka. PolicyCompliant je jedini boolean koji većina pozivalaca želi, kombinujući tri nezavisne odluke: strukturalnu validnost rečnika dozvola, DocMDPCompliant, i FieldMDPCompliant. Držite komponente vidljivim u svom UI umesto da ih sažimate, i primetite da dokument bez DocMDP transformacije ostavlja DocMDPCompliant na True, pošto obično odobravajući potpis ne deklariše nikakvu politiku za kršenje, a agregat ModificationLevel je onda deskriptivan, a 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 trijažu obično želite razlaganje po reviziji umesto sažetka, jer ono kaže kada su u istoriji dokumenta stvari pošle po zlu. Svaki unos u Analysis.Revisions nosi svoj indeks u lancu, cross-reference offset na kom je napisan, sopstveni nivo izmene, i uključene brojeve objekata

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 ocenjuje odvojeno, i to je namerno

Dokument može zadovoljiti DocMDP i i dalje biti nelegitiman, zbog čega je FieldMDPCompliant zaseban boolean, a ne uklopljen u poređenje nivoa. ISO 32000-1 §12.8.2.4 definiše FieldMDP transformaciju, a §12.7.5.5 povezani unos /SigFieldLock, da zamrznu imenovana polja formulara u trenutku potpisivanja, čak i tamo gde dokument u celini i dalje dozvoljava popunjavanje formulara. Popunjavanje polja je akcija nivoa 2; popunjavanje polja koje je potpisnik zaključao je prekršaj bez obzira na nivo. HotPDF čita opseg u THPDFFieldLockAction kao flaAll, flaInclude ili flaExclude, sa flaNone za rezultate bez ikakve politike zaključavanja, a imena u Permissions.FieldNames: flaAll zaključava sve, flaInclude zaključava navedena imena, flaExclude zaključava sve osim njih. Jedan detalj je bitan pri čitanju rezultata, u tome što se samo polja već prisutna u potpisanom snimku prijavljuju u ChangedFieldNames, jer polje kreirano u potpunosti posle potpisivanja nema potpisano stanje kome bi protivrečilo i umesto toga ga hvata DocMDP putanja

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;

Šta ova analiza neće reći

Ne verifikuje potpis. AnalyzeLoadedSignatureRevisions zaključuje o strukturi i dozvolama; da li se potpisan opseg bajtova i dalje heš-uje na vrednost u CMS blob-u, i da li se lanac sertifikata potpisnika oslanja na nešto čemu verujete, odgovaraju VerifyLoadedSignature i VerifyLoadedSignatureWithTrust. Fajl može biti savršeno usklađen sa politikom, a kriptografski bezvredan, tako da dve provere pripadaju jedna uz drugu u bilo kojoj realnoj kapiji prihvatanja. Takođe ne čita nameru unutar tokova sadržaja: stranica čiji je tok sadržaja u potpunosti zamenjen se hvata kao izmena van bele liste, ali analiza vam neće reći da je zamena zamenila iznos plaćanja. Verdikt rmlOther znači da bi čovek trebalo da pogleda, ne da je došlo do prevare, a usaglašen verdikt znači da izmena upada u dozvoljenu kategoriju, ne da je izmena bila željena. Kad vam treba samo ono što je potpisnik deklarisao, bez obilaska revizija, GetLoadedSignaturePermissions vraća rečnike politike same za sebe

Sve opisano ovde radi nativno u Delphi i C++Builder bez ikakvog eksternog servisa za potpisivanje u petlji, što ga čini praktičnim za pokretanje na svakom dolaznom dokumentu umesto samo na onima za koje je neko već posumnjao. Kompletan API potpisa i revizije, uključujući metode dozvola i verifikacije sa kojima se sparuje, deo je HotPDF Component za Delphi i C++Builder