Techninis straipsnis

DocMDP ir FieldMDP: PDF redakcijų auditas Delphi

Pasirašytas PDF, kuris pasikeitė po pasirašymo, nebūtinai yra sugadintas. ISO 32000-1 leidžia priaugančius atnaujinimus virš parašo, ir tik kai kurie iš jų pažeidžia politiką, kurią nustatė pasirašantysis. HotPDF Component Delphi ir C++Builder platformoms atsako į šį klausimą su AnalyzeLoadedSignatureRevisions, kuri klasifikuoja kiekvieną redakciją po pasirašymo ir įvertina ją pagal DocMDP bei FieldMDP. Scenarijus pažįstamas kiekvienam, siunčiančiam sutarčių programinę įrangą: jūsų klientas pasirašo pirkimo sutartį, ją išsiunčia ir gauna atgal su pridėtu priedo puslapiu. Skaitytuvas rodo geltoną juostą, sakančią, kad parašas nepakitęs, bet dokumentas buvo pakeistas po pasirašymo, ir niekas kambaryje negali pasakyti, ar tai įprasta bendro pasirašymo darbo eiga, ar kažkas tyliai redaguoja pasirašytą sutartį

Kas laikoma teisėtu pakeitimu po pasirašymo?

Pakeitimas yra teisėtas, kai jo semantinė kategorija patenka į leidimą, kurį paskelbė sertifikuojantis parašas. ISO 32000-1 §12.8.2.2 apibrėžia DocMDP transformaciją su /P reikšme 1, 2 arba 3: 1 neleidžia jokių pakeitimų, 2 leidžia formos pildymą ir pasirašymą, 3 leidžia formos pildymą, pasirašymą ir anotacijas. HotPDF juos atveria kaip THPDFDocMDPPermission reikšmes dmpNoChanges, dmpFormFillAndSign ir dmpFormFillSignAndAnnotate, o dmpNone rezervuota tikrinimo rezultatams, neturintiems jokios DocMDP transformacijos

Kategorijos surikiuotos, ir ši tvarka — viso patikrinimo variklis. THPDFRevisionModificationLevel turi rmlNone, rmlLongTermValidation, rmlFormFillAndSign, rmlAnnotations, rmlOther, sąmoningai išdėstytus taip, kad didesnis eilės numeris niekada nebūtų mažiau ribojantis. Visas dokumentas susiaurinamas iki didžiausio lygio, pastebėto visose redakcijose po parašo, ir DocMDP palyginimas tampa vienu sveikaskaitiniu testu. Vienas niuansas svarbus anksti: prie dmpNoChanges analizė vis tiek priima rmlLongTermValidation. DSS ir VRI tikrinimo medžiagos ar dokumento laiko žymos pridėjimas prie sertifikuoto failo yra parašo priežiūra, ne dokumento modifikavimas, ir tai laikant pažeidimu būtų sugadinta kiekviena ilgalaikio archyvavimo darbo eiga, kuri egzistuoja

Kaip HotPDF atkuria redakcijų grandinę?

Struktūriškai, ne euristiškai. Pagal ISO 32000-1 §7.5.6 priaugantis atnaujinimas prideda naują kryžminių nuorodų skyrių, kurio /Prev rodo į ankstesnį, todėl HotPDF skaito startxref nuo galo, analizuoja ten esantį skyrių, seka /Prev atgal ir kartoja, grąžindama skyrius nuo seniausio. Šiame cikle yra du saugumo apribojimai, ir abu verta žinoti, tiriant failą, kuris nepavyksta: /Prev, rodantis į jau aplankytą poslinkį, nutraukia ėjimą su aiškia ciklo diagnostika vietoj sukimosi, o grandinė, ilgesnė nei tūkstantis redakcijų, tiesiog atmetama. Abi situacijos pasirodo Analysis.Issue, funkcijai grąžinant False, ir nė vienos iš jų nereikėtų nutylėti, nes cikliškas /Prev yra netaisyklingas ar priešiškas failas, ne neįprastas

Realiuose dokumentuose pasitaiko keturios istorinės formos, ir visos keturios apdorojamos: tradicinės xref lentelės, skaitomos eilutė po eilutės, kryžminių nuorodų srautai, dekompresuoti ir dekoduoti per jų /W ir /Index laukus, hibridinių nuorodų failai, kurių tradicinėje trailer'yje yra /XRefStm raktas, kuris analizuojamas ir sujungiamas į tą pačią redakciją (Office generatoriaus atvejis, aprašytas straipsnyje apie hibridinius kryžminių nuorodų srautus), ir objektai, gyvenantys ObjStm konteineryje, kurie svarbūs, nes šiuolaikinis atnaujinimas dažniausiai talpina pakeistą žodyną suglaudintame sraute, o ne rašo jį tiesiogiai, kaip aprašyta straipsnyje apie objektų srautus ir priaugančius atnaujinimus. Parašas nusako skaidymo tašką: /ByteRange[2] + /ByteRange[3] tampa SignedRevisionLength, o kiekvienas skyrius toje ar už tos ribos yra po pasirašymo. Ar baitų intervalas vis dar tinkamai maišosi, yra atskiras klausimas, į kurį atsako VerifyLoadedSignature, aprašytas straipsnyje apie PDF skaitmeninių parašų tikrinimą

Kaip klasifikuojamas kiekvienas pakeistas objektas

Klasifikacija vyksta objektas po objekto, tada plinta pagal nuorodas. Kiekvienam objekto numeriui, kurį paliečia skyrius po pasirašymo, HotPDF skaito naują kūną ir kūną, koks jis buvo pasirašytoje nuotraukoje; identiškas kūnas yra rmlNone, nes generatoriai iš tikrųjų perrašo objektus jų nekeisdami. Atpažinimo taisyklės sąmoningai siauros. /Type /DocTimeStamp objektas arba toks, kurio /SubFilter yra ETSI.RFC3161, yra rmlLongTermValidation, kaip ir bet kas, pasiekiamas iš katalogo /DSS medžio; /Type /Sig žodynas yra rmlFormFillAndSign. Konteineriams testas yra tai, kurie raktai pajudėjo, ne koks yra objektas: katalogas gali tik įgyti ar pakeisti /DSS, /Extensions arba /AcroForm; AcroForm žodynas — tik /Fields, /SigFlags, /NeedAppearances, /DR, /DA arba /Q; puslapis — tik /Annots; laukas ar valdiklis — tik /V, /AP, /AS arba /M. Viskas už šių rinkinių ribų nukrenta į rmlOther, ir kaip tik taip pagaunamas pridėtas priedo puslapis: puslapio pridėjimas pertvarko puslapių medį taip, kaip nė vienas leidimų sąrašas neapima, ir joks teisėtas formos pildymas į tai nepanašus

Tada lygiai plinta, kiekvienam konteineriui perimant maksimalų lygį iš pakeistų vaikų, į kuriuos jis rodo, kartojant, kol priskyrimas stabilizuojasi. Būtent tai leidžia veikti išvaizdos srautams. Užpildytas teksto laukas perrašo /V ir rodo į naują /AP srautą, o tas srautas savaime yra anoniminis turinio operatorių gabalas be jokio tipo, kurį būtų galima atpažinti; kadangi laukas, kuriam jis priklauso, yra rmlFormFillAndSign, srautas paveldi tą patį lygį, o ne nukrenta į rmlOther. Ta pati sklaida perneša DSS kontekstą į sertifikatų ir atšaukimo srautus, kurie kitaip būtų neklasifikuojami

Kodėl neperskaitomas objektas laikomas pažeidimu?

Todėl, kad alternatyva — validatorius, nugalėtas parašant kažką, ko jis nesupranta. Trys situacijos be jokios išeities baigiasi rmlOther HotPDF: objektas, kurio kūno nepavyko perskaityti iš redakcijos, objektas, kurį redakcija pažymi kaip laisvą, ir objektas, neatitinkantis nei vieno iš aukščiau esančių atpažinimo taisyklių. Kiekvienas užregistruoja konkrečią diagnostiką redakcijos Issue lauke, todėl operatorius gali matyti, kuris objekto numeris davė tą verdiktą

Laisvinimas — aštriausias iš trijų. Redakcija po pasirašymo, pažyminti anksčiau apibrėžtą objektą kaip laisvą, ištrynė turinį iš pasirašyto dokumento, ir joks leidimo lygis pagal §12.8.2.2 to neleidžia; objektų numeriai atsiduria FreedObjectNumbers, o redakcija pakeliama iki rmlOther. Neperskaitomi objektai seka ta pačia logika dėl kitos priežasties. Validatorius, negalintis analizuoti objekto, neturi pagrindo jį laikyti nekenksmingu, o sąžiningas atsakas į tai — ne tyla. Neįprastos, bet nekenksmingos konstrukcijos pranešimas kaip pažeidimo kainuoja žmogaus peržiūrą; priešinga klaida siunčia pasirašytą sutartį su nepastebėtu redagavimu viduje

Verdikto skaitymas Delphi

Kvietimas trumpas. Įkelkite dokumentą, pasirinkite parašo indeksą, perskaitykite įrašą; be parametrų perkrova iš naujo atveria failą, iš kurio buvo įkeltas dokumentas, o TStream perkrova ima iškviesiančiojo pateiktus baitus ir atkuria srauto poziciją prieš grįždama. PolicyCompliant yra vienintelis loginis reikšmys, kurio nori dauguma iškviečiančiųjų, sujungiantis tris nepriklausomus sprendimus: leidimų žodynų struktūrinį galiojimą, DocMDPCompliant ir FieldMDPCompliant. Laikykite komponentus matomus savo sąsajoje vietoj jų suliejimo, ir atkreipkite dėmesį, kad dokumentas be jokios DocMDP transformacijos palieka DocMDPCompliant kaip True, nes paprastas patvirtinimo parašas nedeklaruoja jokios politikos, kurią būtų galima pažeisti, o suminis ModificationLevel tada yra aprašomasis, ne verdiktas

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;

Tikrindami paprastai norite ne santraukos, o suskirstymo pagal redakcijas, nes tai pasako, kada dokumento istorijoje kas nors sugedo. Kiekvienas įrašas Analysis.Revisions neša savo indeksą grandinėje, kryžminių nuorodų poslinkį, kuriuo jis buvo parašytas, savo modifikavimo lygį ir susijusius objektų numerius

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 vertinama atskirai, ir tai sąmoninga

Dokumentas gali atitikti DocMDP ir vis tiek būti neteisėtas, todėl FieldMDPCompliant yra atskiras loginis reikšmis, o ne sulietas su lygio palyginimu. ISO 32000-1 §12.8.2.4 apibrėžia FieldMDP transformaciją, o §12.7.5.5 — susijusį /SigFieldLock įrašą, kad įšaldytų pavadintus formos laukus pasirašymo momentu, net kai visas dokumentas kaip visuma vis dar leidžia formos pildymą. Lauko užpildymas — 2 lygio veiksmas; lauko, kurį pasirašantysis užrakino, užpildymas — pažeidimas nepriklausomai nuo lygio. HotPDF skaito apimtį į THPDFFieldLockAction kaip flaAll, flaInclude arba flaExclude, o flaNone — rezultatams be jokios užrakinimo politikos, ir pavadinimus į Permissions.FieldNames: flaAll užrakina viską, flaInclude užrakina išvardintus pavadinimus, flaExclude užrakina viską, išskyrus juos. Viena detalė svarbi skaitant rezultatus: ChangedFieldNames pranešami tik laukai, jau esantys pasirašytoje nuotraukoje, nes visiškai po pasirašymo sukurtas laukas neturi jokios pasirašytos būsenos, kuriai prieštarauti, ir yra pagaunamas DocMDP keliu

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;

Ko ši analizė jums nepasakys

Ji nepatikrina parašo. AnalyzeLoadedSignatureRevisions sprendžia apie struktūrą ir leidimus; ar pasirašytas baitų intervalas vis dar susimaišo į reikšmę CMS bloke, ir ar pasirašiusiojo sertifikatas grandinėje veda iki kažko, kuo pasitikite, į tai atsako VerifyLoadedSignature ir VerifyLoadedSignatureWithTrust. Failas gali būti visiškai atitinkantis politiką ir kriptografiškai bevertis, todėl abu patikrinimai turi eiti šalia bet kuriame realiame priėmimo vartų taške. Ji taip pat nesuvokia ketinimo turinio srautų viduje: puslapis, kurio turinio srautas buvo pakeistas visiškai, pagaunamas kaip pakeitimas už baltojo sąrašo ribų, bet analizė nepasakys jums, kad pakeitimas pakeitė mokėjimo sumą. rmlOther verdiktas reiškia, kad į tai turėtų pažiūrėti žmogus, ne kad įvyko sukčiavimas, o atitinkantis verdiktas reiškia, kad pakeitimas tinka leistinai kategorijai, ne kad pakeitimas buvo pageidaujamas. Kai jums reikia tik to, ką deklaravo pasirašantysis, be redakcijų ėjimo, GetLoadedSignaturePermissions grąžina politikos žodynus atskirai

Viskas, kas čia aprašyta, veikia natūraliai Delphi ir C++Builder be jokios išorinės pasirašymo paslaugos grandinėje, ir kaip tik tai leidžia praktiškai vykdyti tai kiekviename gaunamame dokumente, o ne tik tuose, kuriais kas nors jau įtarė. Visa parašo ir redakcijų API, įskaitant leidimų ir tikrinimo metodus, su kuriais ji dirba kartu, yra HotPDF Component Delphi ir C++Builder platformoms dalis