Odborný článok

DocMDP a FieldMDP: audit PDF revízií v Delphi

Podpísaný PDF, ktorý sa po podpise zmenil, nie je automaticky pokazený. ISO 32000-1 povoľuje inkrementálne aktualizácie nad podpisom, a iba niektoré z nich porušujú politiku, ktorú podpisujúci nastavil. HotPDF Component pre Delphi a C++Builder odpovedá na túto otázku funkciou AnalyzeLoadedSignatureRevisions, ktorá klasifikuje každú revíziu po podpise a ohodnotí ju voči DocMDP a FieldMDP. Scenár je dôverne známy každému, kto dodáva zmluvný softvér: váš zákazník podpíše kúpnu zmluvu, odošle ju, a dostane ju späť s pripojenou stranou prílohy. Čítačka zobrazí žltý pruh hlásiaci, že podpis je neporušený, ale dokument sa od podpísania zmenil, a nikto v miestnosti nevie povedať, či ide o normálny workflow spolupodpisu, alebo o niekoho, kto potichu edituje podpísanú zmluvu

Čo sa počíta ako legitímna zmena po podpise?

Zmena je legitímna, keď jej sémantická kategória spadá do oprávnenia, ktoré certifikujúci podpis deklaroval. ISO 32000-1 §12.8.2.2 definuje transformáciu DocMDP s hodnotou /P 1, 2 alebo 3: 1 nepovoľuje žiadne zmeny, 2 povoľuje vypĺňanie formulárov a podpisovanie, 3 povoľuje vypĺňanie formulárov, podpisovanie a anotácie. HotPDF ich sprístupňuje ako hodnoty THPDFDocMDPPermission dmpNoChanges, dmpFormFillAndSign a dmpFormFillSignAndAnnotate, pričom dmpNone je vyhradené pre výsledky inšpekcie, ktoré nenesú žiadnu transformáciu DocMDP

Kategórie sú usporiadané, a toto usporiadanie je motorom celej kontroly. THPDFRevisionModificationLevel beží ako rmlNone, rmlLongTermValidation, rmlFormFillAndSign, rmlAnnotations, rmlOther, zámerne usporiadané tak, aby väčší ordinál nikdy nebol menej reštriktívny. Celý dokument sa zredukuje na maximálnu úroveň pozorovanú naprieč každou revíziou po podpise, a porovnanie s DocMDP sa stáva jediným celočíselným testom. Jeden nuans je dôležitý skoro: pri dmpNoChanges analýza stále akceptuje rmlLongTermValidation. Pridanie validačného materiálu DSS a VRI alebo časovej pečiatky dokumentu do certifikovaného súboru je údržba podpisu, nie modifikácia dokumentu, a považovať to za porušenie by rozbilo každý dlhodobý archivačný workflow, ktorý existuje

Ako HotPDF prestavuje reťazec revízií?

Štrukturálne, nie heuristicky. Podľa ISO 32000-1 §7.5.6 inkrementálna aktualizácia pripája novú cross-reference sekciu, ktorej /Prev ukazuje na predchádzajúcu, takže HotPDF číta startxref od chvosta, parsuje sekciu tam, nasleduje /Prev smerom dozadu a opakuje, pričom sekcie vracia najstaršie prvé. V tejto slučke sedia dva bezpečnostné limity, a oba stoja za to poznať pri triage súboru, ktorý zlyhá: /Prev ukazujúci na offset, ktorý už bol navštívený, ukončí prechod s explicitnou diagnostikou cyklu namiesto točenia sa dokola, a reťazec dlhší než tisíc revízií sa rovno odmietne. Oba sa prejavia v Analysis.Issue s funkciou vracajúcou False, a žiadny z nich by sa nemal prehliadať, pretože cyklický /Prev je skôr znetvorený alebo nepriateľský súbor než nezvyčajný

V reálnych dokumentoch sa objavujú štyri historické tvary, a všetky štyri sú ošetrené: tradičné tabuľky xref parsované riadok po riadku, cross-reference streamy dekomprimované a dekódované cez ich polia /W a /Index, hybridne referencované súbory, ktorých tradičný trailer nesie kľúč /XRefStm, ktorý sa parsuje a zlúči do tej istej revízie (prípad Office producenta, pokrytý v článku o hybridných cross-reference streamoch), a objekty žijúce vnútri kontajnera ObjStm, čo je dôležité, pretože moderná aktualizácia zvyčajne vloží zmenený slovník do komprimovaného streamu namiesto priameho zápisu, ako je opísané v článku o objektových streamoch a inkrementálnych aktualizáciách. Podpis kotví rozdelenie: /ByteRange[2] + /ByteRange[3] sa stáva SignedRevisionLength, a každá sekcia na tomto offsete alebo za ním je po podpise. Či rozsah bajtov stále správne hashuje, je samostatná otázka, na ktorú odpovedá VerifyLoadedSignature, pokrytá v článku o overovaní digitálnych podpisov PDF

Ako sa klasifikuje každý zmenený objekt

Klasifikácia beží na úrovni objektu, potom sa šíri pozdĺž referencií. Pre každé číslo objektu, ktorého sa dotkne sekcia po podpise, HotPDF prečíta nové telo a telo tak, ako stálo v podpísanom snímku; identické telo je rmlNone, pretože producenti objekty naozaj prepisujú bez toho, aby ich menili. Rozpoznávače sú zámerne úzke. Objekt /Type /DocTimeStamp, alebo taký, ktorého /SubFilter je ETSI.RFC3161, je rmlLongTermValidation, rovnako ako čokoľvek dosiahnuteľné zo stromu /DSS katalógu; slovník /Type /Sig je rmlFormFillAndSign. Pre kontajnery je testom to, ktoré kľúče sa presunuli, nie čím objekt je: katalóg môže iba získať alebo zmeniť /DSS, /Extensions alebo /AcroForm; slovník AcroForm iba /Fields, /SigFlags, /NeedAppearances, /DR, /DA alebo /Q; strana iba /Annots; pole alebo widget iba /V, /AP, /AS alebo /M. Čokoľvek mimo týchto množín padá do rmlOther, čo je presne to, ako sa zachytí pripojená strana prílohy: pridanie strany preusporiada strom strán spôsobmi, ktoré žiadny zoznam povolených nepokrýva, a žiadne množstvo legitímneho vypĺňania formulára sa mu nepodobá

Potom sa úrovne šíria, pričom každý kontajner dedí maximálnu úroveň zmenených detí, na ktoré ukazuje, iterované, kým sa priradenie nestabilizuje. Práve toto robí appearance streamy funkčnými. Vyplnené textové pole prepíše /V a ukazuje na čerstvý stream /AP, a tento stream je sám osebe anonymný blob operátorov obsahu bez typu, ktorý by bolo možné rozpoznať; keďže pole, ktoré ho vlastní, je rmlFormFillAndSign, stream zdedí tú istú úroveň namiesto toho, aby padol do rmlOther. Rovnaké šírenie prenáša kontext DSS na certifikáty a revokačné streamy, ktoré by inak boli neklasifikovateľné

Prečo sa nečitateľný objekt počíta ako porušenie?

Pretože alternatívou je validátor porazený zápisom niečoho, čomu nerozumie. Tri situácie v HotPDF končia v rmlOther bez odvolania: objekt, ktorého telo sa nepodarilo prečítať z revízie, objekt, ktorý revízia označí ako uvoľnený, a objekt nezodpovedajúci žiadnemu z vyššie uvedených rozpoznávačov. Každá zaznamená konkrétnu diagnostiku v poli Issue revízie, takže operátor vidí, ktoré číslo objektu vyprodukovalo verdikt

Uvoľnenie je z tých troch najostrejšie. Revízia po podpise, ktorá označí predtým definovaný objekt za voľný, odstránila obsah z podpísaného dokumentu, a žiadna úroveň oprávnenia podľa §12.8.2.2 to nedovoľuje; čísla objektov pristanú v FreedObjectNumbers a revízia sa zdvihne na rmlOther. Nečitateľné objekty nasledujú rovnakú logiku z iného dôvodu. Validátor, ktorý nedokáže objekt parsovať, nemá základ na to, aby ho vyhlásil za neškodný, a čestnou odpoveďou na to nie je ticho. Nahlásenie nezvyčajnej, ale neškodnej konštrukcie ako porušenia stojí ľudskú revíziu; opačná chyba odošle podpísanú zmluvu s nepovšimnutou úpravou vnútri

Čítanie verdiktu v Delphi

Volanie je krátke. Načítajte dokument, vyberte index podpisu, prečítajte záznam; bezparametrová preťaženie znovu otvorí súbor, z ktorého bol dokument načítaný, a preťaženie TStream berie volajúcim dodané bajty a pred návratom obnoví pozíciu streamu. PolicyCompliant je jediný boolean, ktorý väčšina volajúcich chce, kombinujúci tri nezávislé rozhodnutia: štrukturálnu platnosť slovníkov oprávnení, DocMDPCompliant, a FieldMDPCompliant. Nechajte tieto zložky viditeľné vo svojom UI namiesto ich zlučovania, a všimnite si, že dokument bez transformácie DocMDP necháva DocMDPCompliant na True, keďže bežný schvaľovací podpis nedeklaruje žiadnu politiku, ktorú by bolo možné porušiť, a agregovaný ModificationLevel je potom skôr popisný než verdiktom

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;

Pri triage zvyčajne chcete rozpis podľa revízií namiesto súhrnu, pretože povie, kedy v histórii dokumentu sa niečo pokazilo. Každý záznam v Analysis.Revisions nesie svoj index v reťazci, offset cross-reference, na ktorom bol zapísaný, vlastnú úroveň modifikácie a čísla objektov, ktorých sa to týka

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 sa posudzuje oddelene, a je to zámer

Dokument môže vyhovovať DocMDP a napriek tomu byť nelegitímny, a preto je FieldMDPCompliant samostatný boolean namiesto toho, aby bol zlúčený do porovnania úrovní. ISO 32000-1 §12.8.2.2 definuje transformáciu FieldMDP, a §12.7.5.5 súvisiacu položku /SigFieldLock, na zmrazenie pomenovaných formulárových polí v momente podpisu, aj tam, kde dokument ako celok stále povoľuje vypĺňanie formulárov. Vyplnenie poľa je akcia úrovne 2; vyplnenie poľa, ktoré podpisujúci uzamkol, je porušenie bez ohľadu na úroveň. HotPDF číta rozsah do THPDFFieldLockAction ako flaAll, flaInclude alebo flaExclude, s flaNone pre výsledky bez žiadnej politiky uzamknutia, a názvy do Permissions.FieldNames: flaAll uzamkne všetko, flaInclude uzamkne uvedené názvy, flaExclude uzamkne všetko okrem nich. Jeden detail je dôležitý pri čítaní výsledkov, a to že v ChangedFieldNames sa hlásia iba polia už prítomné v podpísanom snímku, pretože pole vytvorené úplne po podpise nemá žiadny podpísaný stav, ktorý by mohlo protirečiť, a zachytí ho namiesto toho cesta 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;

Čo vám táto analýza nepovie

Neoveruje podpis. AnalyzeLoadedSignatureRevisions uvažuje o štruktúre a oprávneniach; či podpísaný rozsah bajtov stále hashuje na hodnotu v CMS bloku, a či certifikát podpisujúceho reťazí na niečo, čomu dôverujete, odpovedajú VerifyLoadedSignature a VerifyLoadedSignatureWithTrust. Súbor môže byť dokonale v súlade s politikou a zároveň kryptograficky bezcenný, takže tieto dve kontroly patria vedľa seba v akejkoľvek reálnej vstupnej bráne. Tiež nečíta zámer vnútri obsahových streamov: strana, ktorej obsahový stream bol kompletne nahradený, sa zachytí ako zmena mimo zoznamu povolených, ale analýza vám nepovie, že nahradenie zamenilo platobnú sumu. Verdikt rmlOther znamená, že by sa mal pozrieť človek, nie že došlo k podvodu, a súladný verdikt znamená, že zmena zapadá do povolenej kategórie, nie že zmena bola chcená. Keď potrebujete iba to, čo podpisujúci deklaroval, bez prechodu revízií, GetLoadedSignaturePermissions vráti slovníky politiky samostatne

Všetko opísané tu beží natívne v Delphi a C++Builder bez akejkoľvek externej podpisovej služby v slučke, čo je to, čo robí praktickým spustiť to na každom prichádzajúcom dokumente namiesto len na tých, ktoré niekto už podozrieval. Úplné API podpisov a revízií, vrátane metód oprávnení a overovania, s ktorými sa páruje, je súčasťou HotPDF Component pre Delphi a C++Builder