Tehnički članak

PDFium Component DocMDP: kako widget /P krije izmjene

U PDFium Component buildovima prije v3.126.2, TPdf.AnalyzeSignatureRevisions mogao je stvarnu izmjenu sadržaja stranice ocijeniti kao dopuštenu promjenu anotacije pod DocMDP P=3, jer je njegov graf uloga revizija /P povratnu referencu signature widgeta na njegovu stranicu tretirao kao vlasništvo. Od v3.126.2 PDFium Component razdvaja navigacijske grane od grana stvarnog vlasništva, pa sadržaj stranice ostaje sadržaj stranice. Bug izvještaj iza ovog popravka na papiru izgleda bezopasno. Certificirani ugovor dopušta anotacije, protustranka doda jedno inkrementalno spremanje, i analyzer kaže da je svaka kasnija promjena dopuštena. Onda netko usporedi renderirane stranice i iznos plaćanja na stranici 2 je drugačiji

Ovaj članak nastavak je iz očiju napadača na pregled analize promjena revizija nakon potpisa, pa preskače osnove obnove revizija i DocMDP ocjenjivanja i ide pravo na graf objekata: kako je vlasništvo modelirano, zašto smjer grane odlučuje o sigurnosnoj odluci, što je promijenjeno u v3.126.2 i kako revizirati vlastitu logiku prihvaćanja

Zašto je izmjena stranice prošla kao promjena anotacije pod DocMDP P=3?

Izmjena stranice prošla je jer je stari graf uloga slijedio svaku indirektnu referencu u rječniku kao da referencirani objekt pripada onome koji ga referencira, a signature widget pokazuje natrag na svoju stranicu. Rječnik anotacije nosi /P, indirektnu referencu na objekt stranice na kojem sjedi (ISO 32000-1 §12.5.2). Taj unos je navigacijski nagovještaj. Widget ne posjeduje stranicu; stranica posjeduje widget kroz svoje polje /Annots

Analyzer svakom objektu dodjeljuje skup bitova uloga prije nego ocijeni kasnije promjene: stranica, anotacija, forma i validacijski materijal. Korijenski objekti ulogu dobivaju iz vlastitog rječnika, a uloga se zatim širi na sve što referenciraju. U staroj propagaciji lanac je išao ovako:

  1. Signature widget je /Subtype /Widget s /FT /Sig, pa dobiva ulogu anotacije
  2. Widgetov /P gura ulogu anotacije na rječnik stranice, koji već ima ulogu stranice
  3. Stranica obje uloge gura u /Contents, /Resources i, kroz /Parent, gore u Pages stablo i preko svake sestrinske stranice
  4. Rječnik toka sadržaja poput << /Length 812 >> nema /Type, pa je klasifikator pao natrag na bitove uloga i provjerio ulogu anotacije prije uloge stranice
PDFium Component dijagram DocMDP grafa uloga prije v3.126.2 u kojem /P povratna referenca signature widgeta gura ulogu anotacije na rječnik stranice, stranica je širi kroz /Contents na tok sadržaja bez unosa /Type, klasifikator ispisuje prckAnnotation, a P=3 ocjena vraća prdAllowed
Prije v3.126.2 graf uloga svaku je indirektnu referencu tretirao kao vlasništvo, pa je widgetov unos /P gurao ulogu anotacije na stranicu i prava izmjena stranice izlazila je iz analyzera kao dopuštena promjena anotacije

Izmijenjeni tok sadržaja zato je izašao kao prckAnnotation. Pod ISO 32000-1 §12.8.2.2, DocMDP P=3 dopušta promjene anotacija, pa je odluka bila prdAllowed, a izvještaj se zbrojio u prasAllowed. Ista datoteka pod P=2 bila je odbijena, ali samo slučajno: P=2 zabranjuje promjene anotacija, pa je pogrešno označeni tok odbijen zbog krivog razloga. Fiksna petlja propagacije od četiri prolaza dodala je i drugu slabost. Sadržaj koji stiže kroz indirektno polje, ili kroz dugi lanac čiji se brojevi objekata vraćaju unatrag, možda nikad ne dobije nikakvu ulogu

Zašto validator potpisa mora pitati tko posjeduje objekt?

Validator potpisa mora pitati tko posjeduje objekt jer PDF inkrementalna ažuriranja (ISO 32000-1 §7.5.6) dopuštaju svakome da doda reviziju koja redefinira postojeći broj objekta, a redefinirano tijelo ne najavljuje što je. Potpis i dalje prolazi verifikaciju, jer pokriva samo bajtove vlastite revizije. Svaka obrana od manipulacije nakon potpisa zato ovisi o tome da se svaki promijenjeni objekt mapira u strukturu koja ga koristi, pa da se pita je li potpisnik dopustio da se ta struktura mijenja

Nekoliko objavljenih klasa napada radi baš u tom prorezu. Napadi inkrementalnim spremanjem dodaju reviziju koja mijenja sadržaj stranice i oslanjaju se na to da verifier provjerava samo potpisani bajtni raspon. Shadow napadi posađuju skriveni sadržaj prije potpisivanja i aktiviraju ga poslije malom, nevina izgledajućom promjenom. Napadi na certificirane dokumente zlostavljaju činjenicu da P=2 i P=3 izričito dopuštaju neke kasnije izmjene, pa zabranjenu izmjenu obuku u dopuštenu. Verifier koji klasificira objekte po oznakama poput /Type /Annot, ili po bilo kojem putu referenci koji slučajno dođe do njih, izložen je trećoj klasi: napadaču treba samo jedna dopuštena struktura koja može doći do zabranjene

Zato pitanje nije što se objekata promijenilo, nego tko ih posjeduje. Tok sadržaja dohvaćen sa stranice kroz /Contents sadržaj je stranice ma što sve pokazivalo na njega. Anotacija koja pokazuje natrag na stranicu kroz /P govori gdje anotacija živi, ne što posjeduje

Kako PDFium Component v3.126.2 modelira vlasništvo?

PDFium Component v3.126.2 povratne reference tretira kao navigaciju, drži ih izvan propagacije uloga, i odlučuje koji ključevi računaju se kao navigacija prema strukturalnoj ulozi rječnika koji ih drži, a ne prema samom imenu ključa. Tablica sažima navigacijske ključeve koji više ne nose vlasništvo

Vlasnički rječnikKljučevi tretirani kao navigacijaReferenca na specifikaciju
Stranica ili Pages čvor/Parent, /Kids, /AnnotsISO 32000-1 §7.7.3
Anotacija ili widget/PISO 32000-1 §12.5.2
Rječnik widgeta ili polja/ParentISO 32000-1 §12.7.3
PDFium Component v3.126.2 graf objekata koji razdvaja grane stvarnog vlasništva poput /Contents i /Annots, koje šire uloge stranice i anotacije, od navigacijskih grana poput widgetova /P, koje ne nose uloge, uz navigacijske ključeve po vlasničkom rječniku i prckPageContent zadržan na toku sadržaja čak i pod krivotvorenim /Type
v3.126.2 drži povratne reference izvan propagacije uloga: uloge putuju samo kroz stvarno vlasništvo, pa tok sadržaja ostaje sadržaj stranice, a /P nagovještaj ne odlučuje ništa

Globalno filtriranje po imenu ključa stvorilo bi novu rupu. Resurs fonta ili XObject legitimno se može zvati /P, /Parent ili /Annots, a /Resources rječnik koji bi svoj unos /P izbacio iz propagacije dopustio bi napadaču da sakrije stranicom posjedovani XObject iza nevinog imena resursa. U v3.126.2 navigacijski filter primjenjuje se samo kad vlasnički rječnik stvarno jest stranica, Pages čvor, anotacija, widget ili polje. Ako jedan od tih rječnika nosi duplicirani navigacijski ključ, poput dva unosa /P u widgetu, analyzer ne pogađa koju bi kopiju preglednik koristio; izgradnja uloga pada, a potpis postaje Indeterminate

Nekoliko daljnjih pravila zatvara preostale rute preoznačavanja:

  • Pages čvorovi sami po sebi korijeni su uloge stranice, pa resursi naslijeđeni iz Pages stabla (ISO 32000-1 §7.7.3.4) ulaze u kontekst stranice kroz stvarno vlasništvo, ne kroz /Parent šetnju od dječje stranice
  • Uloga anotacije ili forme koja stigne do rječnika kataloga, Pages čvora, stranice, anotacije ili polja tu staje, jer ti strukturalni objekti uspostavljaju vlastite uloge, a uloga dolazećeg sadržaja ne smije ih nadjačati
  • Uloga stranice autoritativna je tijekom klasifikacije: objekt u vlasništvu stranice jest prckPageContent čak i ako ga kasnija revizija prereše s krivotvorenim /FT, oznakom /Type /Annot, ili ga podijeli s appearance tokom
  • Form XObject korišten samo kao appearance polja ili anotacije zadržava svoju kategoriju forme ili anotacije, pa se uobičajena regeneracija appearancea nakon popunjavanja forme i dalje ocjenjuje pod normalnim pravilima dopuštenja
  • Widget bez vlastitog /FT razrješuje naslijeđeni tip polja kroz lanac /Parent, a nerazrješiv lanac obara izgradnju uloga umjesto da po zadanom odabere anotaciju
  • Bitovi uloga iz svake kasnije revizije spajaju se u uloge pokrivene revizije, pa kasnije ažuriranje ne može obrisati raniji odnos vlasništva stranice time što prvo odvoji tok, a zatim ga izmijeni

Fiksna točka umjesto fiksnog broja prolaza

Dohvatljivost uloga u v3.126.2 radi kao radni red koji se ponavlja dok nijedan objekt ne dobije novi bit uloge, što je prava fiksna točka bez obzira na dubinu lanca ili numeriranje objekata. Indirektna polja poput polja /Contents spremljenog kao vlastiti objekt također se prolaze. Svaki objekt može dobiti najviše četiri različita bita uloga, pa je red ograničen na četiri unosa po broju objekta; prekoračenje tog proračuna podiže prrResourceLimitExceeded. Referenca na slobodni objekt, nepoklapanje generacije ili slomljeno zaglavlje objekta podiže prrMalformedRevisionChain, a sadržaj unutar komprimiranog toka objekata podiže prrCompressedObjectUnresolved. Svaki od tih padova završava s prasIndeterminate, nikad s dopuštenom odlukom, i kad pad nastane tijekom izgradnje uloga pokrivene revizije, potpis uopće ne izvještava Changes

Sljedeća rutina ispisuje izmjene sadržaja stranice koje prežive ovu analizu. Promjena prckPageContent nikad se ne ocjenjuje prdAllowed: DocMDP P=1, 2 ili 3 čini je prdDisallowed, a potpis bez DocMDP-a ocjenjuje je prdSuspicious

uses
  SysUtils, TypInfo, PDFium, FPdfPades;

procedure ListPageContentEdits(const FileName: string);
const
  ShadowTag: array[Boolean] of string = (' (unreferenced shadow)', '');
var
  Pdf: TPdf;
  Report: TPadesRevisionAnalysisReport;
  Sig: TPadesSignatureRevisionAnalysis;
  Change: TPadesRevisionObjectChange;
  i, j: Integer;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := FileName;
    Pdf.Active := True;
    Report := Pdf.AnalyzeSignatureRevisions;   // zapis, nema što osloboditi
    for i := 0 to High(Report.Signatures) do
    begin
      Sig := Report.Signatures[i];
      if not (prrPageContentChanged in Sig.Risks) then
        Continue;
      Writeln(Format('Signature %d (DocMDP P=%d): page content changed',
        [Sig.SignatureIndex, Sig.DocMdpPermission]));
      for j := 0 to High(Sig.Changes) do
      begin
        Change := Sig.Changes[j];
        if Change.Kind <> prckPageContent then
          Continue;
        Writeln(Format('  revision %d  object %d %d R  %s%s',
          [Change.RevisionIndex, Change.ObjectNumber, Change.Generation,
           GetEnumName(TypeInfo(TPadesRevisionDecision), Ord(Change.Decision)),
           ShadowTag[Change.IsAuthoritative]]));
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

Što se dogodi kad FieldMDP i anotacija dijele jedan objekt?

Kad /V polja i /Contents anotacije pokazuju na isti indirektni objekt, v3.126.2 drži FieldMDP bravu na snazi iako se promjena klasificira kao izmjena anotacije. Scenarij je lako sastaviti ručno: potpisnik zaključa polje Total s FieldMDP-om (ISO 32000-1 §12.8.2.4), a napadač natjera da /Contents tekstovne anotacije referencira isti string objekt koji drži vrijednost polja. Pod P=3 izmjena anotacije dopuštena je, pa je prije popravka prepisivanje tog dijeljenog stringa promijenilo zaključanu vrijednost polja s dopuštenom odlukom

Objekt sada nosi i ulogu anotacije i ulogu forme, a odluka anotacije ponovno provjerava stranu forme svaki put kad potpis ima FieldMDP transformaciju:

  • Pod P=2 promjena anotacije odbija se odmah, točno kao i prije
  • S FieldMDP All, svako je polje zaključano, pa je dijeljena promjena prdDisallowed
  • S FieldMDP Include ili Exclude, analyzer dijeljeni skalar ne može vratiti do jednog imena polja, pa je odluka prdIndeterminate umjesto pogađanja
  • Bez FieldMDP-a primjenjuje se P=3 pravilo anotacije i promjena ostaje dopuštena
PDFium Component FieldMDP dijagram odluke u kojem zaključano polje Total /V i anotacija /Contents referenciraju isti indirektni objekt, razgranavajući se preko DocMDP P=2, FieldMDP All, FieldMDP Include ili Exclude i bez FieldMDP-a na prdDisallowed, prdIndeterminate ili prdAllowed presude za istu dijeljenu izmjenu
Kad jedan indirektni objekt nosi i ulogu anotacije i ulogu forme, odluka anotacije ponovno provjerava FieldMDP bravu, pa ista izmjena varira od dopuštene do zabranjene do neodređene

Jedan detalj izvještavanja bitan je za kapijski kod. Dijeljeni slučaj izvještava se kao Kind = prckAnnotation s Decision = prdIndeterminate, a prrFieldMdpUnresolved u skup rizika dodaje se samo za promjene klasificirane kao polja forme. Kapija koja traži prrFieldMdpUnresolved a ignorira Status promašuje ovaj slučaj u potpunosti

Kako Delphi kod treba raditi fail closed na analizi revizija?

Delphi kod treba prihvatiti potpisani dokument samo kad je status analize prasNoLaterChanges ili prasAllowed i nema strukturalnog rizika, a prasIndeterminate i prasSuspicious treba tretirati kao nepovjerljive, ne kao upozorenja za logirati i pustiti. Indeterminate znači da analyzer nije mogao dokazati da su kasnije revizije bile dopuštene; za napadača je unos koji pouzdano proizvodi Indeterminate jednako koristan kao onaj koji proizvodi Allowed ako ga vaš kod pusti. Globalni AnalyzePadesSignatureRevisions prima bilo koji TStream i čita ga od pozicije 0, što odgovara upload handlerima kojima nikad ne treba renderirati dokument

uses
  Classes, SysUtils, TypInfo, FPdfPades;

const
  // Duplikatne definicije bilježe se bez spuštanja Statusa
  BlockingRisks: TPadesRevisionRisks = [prrMalformedRevisionChain,
    prrDuplicateObjectDefinition, prrUnreferencedObjectDefinition,
    prrSignatureObjectRedefined, prrCompressedObjectUnresolved,
    prrResourceLimitExceeded];

function SignedRevisionsAcceptable(const FileName: string;
  out Reason: string): Boolean;
var
  Source: TFileStream;
  Report: TPadesRevisionAnalysisReport;
begin
  Result := False;
  Reason := '';
  Source := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    Report := AnalyzePadesSignatureRevisions(Source);
  finally
    Source.Free;
  end;
  if Report.SignatureCount = 0 then
  begin
    Reason := 'no signature anchors the analysis';
    Exit;
  end;
  if Report.Risks * BlockingRisks <> [] then
  begin
    Reason := 'structural risk in the revision chain';
    Exit;
  end;
  case Report.Status of
    prasNoLaterChanges, prasAllowed:
      Result := True;
  else
    // prasIndeterminate i prasSuspicious odbijenice su, ne upozorenja
    Reason := 'revision status ' +
      GetEnumName(TypeInfo(TPadesRevisionAnalysisStatus), Ord(Report.Status));
  end;
end;

Dvije granice vrijedi izreći ravnim jezikom. TPadesRevisionAnalysisReport ne govori ništa o CMS cjelovitosti ni o vjerodostojnosti certifikata, pa ova kapija stoji uz kriptografsku i trust validaciju, ne umjesto njih. A ispravan graf vlasništva ne čini P=3 sigurnim za svaki tijek rada. P=3 stvarno dopušta anotacije, i anotacija s neprozirnim appearanceom može prekriti potpisani tekst a da ne dotakne nijedan tok sadržaja. Ako su vaši certificirani dokumenti ugovori, a ne primjerci za pregled, ili certificirajte s P=2 ili usmjerite dopuštene promjene anotacija na čovjeka, kao u ovom helperu:

function AllowedAnnotationEditsUnderP3(
  const Report: TPadesRevisionAnalysisReport): Integer;
var
  i, j: Integer;
begin
  Result := 0;
  for i := 0 to High(Report.Signatures) do
    if Report.Signatures[i].DocMdpPermission = 3 then
      for j := 0 to High(Report.Signatures[i].Changes) do
        if (Report.Signatures[i].Changes[j].Kind = prckAnnotation) and
           (Report.Signatures[i].Changes[j].Decision = prdAllowed) then
          Inc(Result);
end;

Kontrolni popis za audit revizija potpisa

Koristite ovaj popis da provjerite je li vaš verifikacijski pipeline bio izložen i radi li sada fail closed:

  • Buildovi PDFium Component prije v3.126.2 mogli su izvještavati prasAllowed za izmjene sadržaja stranice u DocMDP P=3 dokumentima; ponovno pokrenite TPdf.AnalyzeSignatureRevisions nad certificiranim P=3 datotekama koje su prihvatili stariji buildovi
  • Ponovno provjerite P=3 dokumente s FieldMDP bravama gdje vrijednost polja i anotacija možda dijele indirektni objekt
  • Prihvaćajte samo prasNoLaterChanges i prasAllowed; tretirajte prasIndeterminate i prasSuspicious kao nepovjerljive
  • Testirajte Report.Risks kao i Report.Status, jer prrDuplicateObjectDefinition sam po sebi ne mijenja status
  • Ne čitajte prazno polje Changes kao čist rezultat kad je status potpisa Indeterminate; pala izgradnja uloga ne izvještava promjene
  • Ne oslanjajte se samo na prrFieldMdpUnresolved za hvatanje FieldMDP problema, jer se dijeljeni slučaj anotacije pokazuje samo kroz odluku i status
  • Odlučite trebaju li dopuštene promjene anotacija pod P=3 ljudski pregled u vašem tijeku rada
  • Analizirajte izvorne bajtove datoteke; dokument prereisan s SaveAs više ne sadrži lanac revizija

Analiza revizija jedan je sloj provjere potpisa. Uporedite je s pregledom PDF digitalnih potpisa i PAdES razina za rječnik i baseline razinu, te s širom PDF auditom sigurnosnih rizika za JavaScript, launch akcije i ugrađene datoteke. TPdf.AnalyzeSignatureRevisions, AnalyzePadesSignatureRevisions i graf uloga svjestan vlasništva opisan ovdje isporučuju se u PDFium Component za Delphi, C++Builder i Lazarus