PDFium Component variantuose iki v3.126.2 TPdf.AnalyzeSignatureRevisions tikrą puslapio turinio redagavimą galėjo įvertinti kaip leistą anotacijų pakeitimą po DocMDP P=3, nes jos revizijų rolių grafas parašo valdiklio /P atgalinę nuorodą į jo puslapį laikė nuosavybe. Nuo v3.126.2 PDFium Component navigacijos briaunas atskiria nuo turimos apkrovos briaunų, tad puslapio turinys lieka puslapio turiniu. Už šį sutvarkymą stovinti klaidos ataskaita popieriuje atrodo nepavojinga. Sertifikuotas sutartis leidžia anotacijas, kontrahentas prideda vieną papildomą išsaugojimą, ir analizatorius sako, kad visi vėlesni pakeitimai leidžiami. Tada kas nors sulygina atvaizduotus puslapius, ir mokėjimo suma 2 puslapyje kita
Šis straipsnis – puolėjo akimis parašyta dalis prie apžvalgos revizijų pakeitimų analizė po parašo, tad praleidžia revizijų atkūrimo ir DocMDP vertinimo pagrindus ir eina tiesiai į objektų grafą: kaip buvo modeliuota nuosavybė, kodėl briaunos kryptis nusprendžia saugumo verdiktą, kas pasikeitė v3.126.2 ir kaip patikrinti savą priėmimo logiką
Kodėl puslapio redagavimas praeidavo kaip anotacijų pakeitimas po DocMDP P=3?
Puslapio redagavimas praeidavo, nes senasis rolių grafas sekė kiekvieną netiesioginę nuorodą žodyne tarsi nurodytas objektas priklausytų nurodančiajam, o parašo valdiklis rodo atgal į savo puslapį. Anotacijos žodynas neša /P – netiesioginę nuorodą į puslapio objektą, ant kurio tupi (ISO 32000-1 §12.5.2). Tas įrašas – navigacijos užuomina. Valdiklis puslapio nesavaldo; puslapis valdo valdiklį per savą /Annots masyvą
Analizatorius kiekvienam objektui priskiria rolių bitų aibę, dar neįvertinęs vėlesnių pakeitimų: puslapio, anotacijos, formos ir patikros medžiagos. Šakniniai objektai savo rolę gauna iš savo žodyno, ir rolė paskui plinta į viską, į ką jie rodo. Senojoje sklaidoje grandinė ėjo taip:
- Parašo valdiklis yra
/Subtype /Widgetsu/FT /Sig, tad gauna anotacijos rolę - Valdiklio
/Panotacijos rolę nustumia ant puslapio žodyno, kuriame puslapio rolė jau yra - Puslapis abi roles nustumia į
/Contents,/Resourcesir, per/Parent, aukštyn į Pages medį ir paskui į kiekvieną seserinį puslapį - Turinio srauto žodynas, toks kaip
<< /Length 812 >>,/Typeneturi, tad klasifikatorius grįždavo prie rolių bitų ir tikrindavo anotacijos rolę anksčiau už puslapio rolę
Pakeistas turinio srautas todėl išeidavo kaip prckAnnotation. Pagal ISO 32000-1 §12.8.2.2 DocMDP P=3 leidžia anotacijų pakeitimus, tad sprendimas buvo prdAllowed, o ataskaita susisuksi į prasAllowed. Tas pats failas po P=2 būdavo atmetamas, bet tik atsitiktinai: P=2 draudžia anotacijų pakeitimus, tad klaidingai pažymėtas srautas būdavo atmetamas dėl neteisingos priežasties. Fiksuota keturių praėjimų sklaidos kilpa pridėdavo antrą silpnumą. Apkrova, pasiekta per netiesioginį masyvą ar per ilgą grandinę, kurios objektų numeriai bėga atgal, gaudavo likti visai be rolės
Kodėl parašo tikrintojas turi klausti, kam priklauso objektas?
Parašo tikrintojas turi klausti, kam priklauso objektas, nes PDF papildomi atnaujinimai (ISO 32000-1 §7.5.6) leidžia bet kam pridėti reviziją, persibrežiančią esamą objekto numerį, o persibrežtas turinys neskelbia, kas jis toks. Parašas vis tiek patvirtinamas, nes dengia tik savos revizijos baitus. Kiekviena gynyba nuo manipuliacijos po pasirašymo todėl remiasi tuo, kad kiekvienas pakeistas objektas susiejamas su struktūra, kuri juo naudojasi, ir tada klausiama, ar pasirašęs leido tai struktūrai keistis
Keli paskelbti atakų klasės darbai būtent toje spragoje. Papildomo išsaugojimo atakos prideda reviziją, sukeičiančią puslapio turinį, ir remiasi tikrintoju, tikrinančiu tik pasirašytą baitų ruožą. Šešėlinės atakos pasodina paslėptą turinį iki pasirašymo ir suaktyvina jį vėliau nedideliu, nekaltai atrodančiu pakeitimu. Atakos prieš sertifikuotus dokumentus piktnaudžiauja tuo, kad P=2 ir P=3 aiškiai leidžia kai kuriuos vėlesnius redagavimus, ir tada draudžiamą redagavimą aprengia leistu. Tikrintojas, klasifikuojantis objektus pagal etiketes, tokias kaip /Type /Annot, arba pagal bet kurį atsitiktinai juos pasiekiantį nuorodų kelią, yra atviras trečiajai klasei: puolėjui užtenka vienos leistos struktūros, gebančios pasiekti draudžiamąją
Todėl klausimas ne tas, kurie objektai pasikeitė, o tas, kam jie priklauso. Turinio srautas, pasiektas iš puslapio per /Contents, yra puslapio turinys, kad ir kas dar į jį rodytų. Anotacija, atgal į puslapį rodanti per /P, sako, kur anotacija gyvena, o ne ką ji savo
Kaip PDFium Component v3.126.2 modeliuoja nuosavybę?
PDFium Component v3.126.2 atgalines nuorodas laiko navigacija, laiko jas už rolių sklaidos ribų, o tai, kurie raktai skaitosi navigacija, sprendžia iš žodyno, laikančio tuos raktus, struktūrinės rolės, o ne vien iš rakto pavadinimo. Lentelė apibendrina navigacijos raktus, kurie nebeneša nuosavybės
| Nuosavybės žodynas | Raktai, laikomi navigacija | Specifikacijos nuoroda |
|---|---|---|
| Puslapio arba Pages mazgas | /Parent, /Kids, /Annots | ISO 32000-1 §7.7.3 |
| Anotacija arba valdiklis | /P | ISO 32000-1 §12.5.2 |
| Valdiklio arba lauko žodynas | /Parent | ISO 32000-1 §12.7.3 |
Visuotinis filtravimas pagal rakto pavadinimą būtų padaręs naują skylę. Šriftas arba XObject išteklius teisėtai gali vadintis /P, /Parent arba /Annots, o /Resources žodynas, išmetęs savą /P įrašą iš sklaidos, leistų puolėjui paslėpti puslapio valdomą XObject už nekaltai skambančio ištekliaus pavadinimo. v3.126.2 navigacijos filtras taikomas tik tada, kai nuosavybės žodynas iš tiesų yra puslapis, Pages mazgas, anotacija, valdiklis arba laukas. Jei vienas tokių žodynų neša padubuotą navigacijos raktą, pavyzdžiui du /P įrašus valdiklyje, analizatorius nespėlioja, kurio kopijos imtųsi peržiūrovas; rolių kūrimas žlunga, ir parašas tampa Indeterminate
Keli tolesni variantai uždarė likusius perženklinimo kelius:
- Pages mazgai patys savaime yra puslapio rolės šaknys, tad iš Pages medžio paveldėti ištekliai (ISO 32000-1 §7.7.3.4) į puslapio kontekstą patenka per tikrą nuosavybę, o ne per
/Parentėjimą nuo vaiko puslapio - Anotacijos ar formos rolė, atkeliavusi į katalogo, Pages mazgo, puslapio, anotacijos ar lauko žodyną, ten sustoja, nes tie struktūriniai objektai patys nustato savas roles, ir atkeliaujanti apkrovos rolė jų nepakeisti negali
- Puslapio rolė klasifikavimo metu sprendžia: puslapio valdomas objektas lieka
prckPageContent, net jei vėlesnė revizija jį perrašo su suklastota/FT,/Type /Annotetikete arba dalijasi juo su išvaizdos srautu - Form XObject, naudojamas vien kaip lauko ar anotacijos išvaizda, išlaiko savo formos ar anotacijos kategoriją, tad paprastas išvaizdos atkūrimas po formos užpildymo vis tiek vertinamas pagal įprastas leidimų taisykles
- Valdiklis be sava
/FTpaveldėtą lauko tipą išspręndžia per/Parentgrandinę, o neišsprendžiama grandinė žlugdo rolių kūrimą vietoj numatymo į anotaciją - Rolių bitai iš kiekvienos vėlesnės revizijos suliejami į dengiamosios revizijos roles, tad vėlesnis atnaujinimas negali ištrinti ankstesnio puslapio nuosavybės santykio, pirmiau atskyręs srautą ir tada jį paredagavęs
Stovis vietoj fiksuoto praėjimų skaičiaus
Rolių pasiekiamumas v3.126.2 lekia kaip darbų eilė, kartojanti, kol joks objektas daugiau negauna naujo rolių bito – tikras stovis, nesvarbu, kokia grandinės gelmė ar objektų numeracija. Netiesioginiai masyvai, tokie kaip /Contents masyvas, saugomas kaip sava objektas, taip pat apeinami. Kiekvienas objektas gali gauti daugiausia keturis skirtingus rolių bitus, tad eilė ribojama keturiais įrašais objekto numeriui; viršijus tą biudžetą keliama prrResourceLimitExceeded. Nuoroda į laisvą objektą, kartos nesutapimas ar sugadinta objekto antraštė kelia prrMalformedRevisionChain, o apkrova suspausto objektų srauto viduje kelia prrCompressedObjectUnresolved. Kiekviena šių nesėkmių pasibaigia prasIndeterminate, niekada leistu verdiktu, o kai nesėkmė iškyla kuriant dengiamosios revizijos roles, parašas visai nepraneša jokių Changes
Ši procedūra išvardija puslapio turinio redagavimus, išgyvenančius šią analizę. prckPageContent pakeitimas niekada nevertinamas prdAllowed: DocMDP P=1, 2 arba 3 padaro jį prdDisallowed, o parašas be DocMDP įvertina jį 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; // įrašas, nieko atlaisvinti
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;
Kas nutinka, kai FieldMDP ir anotacija dalijasi vienu objektu?
Kai lauko /V ir anotacijos /Contents rodo į tą patį netiesioginį objektą, v3.126.2 FieldMDP užraktą palaiko galiojantį, nors pakeitimas klasifikuojamas kaip anotacijos redagavimas. Scenarijų lengva susukti rankomis: pasirašęs užrakina Total lauką su FieldMDP (ISO 32000-1 §12.8.2.4), o puolėjas padaro, kad tekstinės anotacijos /Contents rodo į tą patį eilutės objektą, laikantį lauko reikšmę. Po P=3 anotacijos redagavimas leidžiamas, tad iki pataisymo tos bendros eilutės perrašymas pakeisdavo užrakinto lauko reikšmę su leistu verdiktu
Objektas dabar neša ir anotacijos, ir formos roles, o anotacijos sprendimas bet kada, kai parašas turi FieldMDP transformaciją, dar kartą patikrina formos pusę:
- Po P=2 anotacijos pakeitimas draudžiamas be pasigailėjimo – tiksliai kaip ir anksčiau
- Su FieldMDP
Allužrakinti visi laukai, tad bendras pakeitimas yraprdDisallowed - Su FieldMDP
IncludearbaExcludeanalizatorius bendro skalioriaus negali atsekti iki vieno lauko pavadinimo, tad sprendimas –prdIndeterminate, o ne spėjimas - Be FieldMDP galioja P=3 anotacijos taisyklė, ir pakeitimas lieka leistinas
Viena ataskaitų detalė svarbi vartų kodui. Bendras atvejis pranešamas kaip Kind = prckAnnotation su Decision = prdIndeterminate, o prrFieldMdpUnresolved į rizikų aibę pridedamas tik pakeitimams, klasifikuotiems kaip formos laukai. Vartai, ieškantys prrFieldMdpUnresolved ir ignoruojantys Status, šio atvejo nepastebi visiškai
Kaip Delphi kodas turėtų kloti saugiai revizijų analizėje?
Delphi kodas pasirašytą dokumentą turėtų priimti tik tada, kai analizės būsena yra prasNoLaterChanges arba prasAllowed ir nėra jokios struktūrinės rizikos, o prasIndeterminate ir prasSuspicious turėtų laikyti nepasitikėjimo reikšmėmis, o ne į žurnalą įrašomais ir praleidžiamais įspėjimais. Indeterminate reiškia, kad analizatorius nepajėgė įrodyti, jog vėlesnės revizijos buvo leistos; puolėjui įvestis, patikimai duodanti Indeterminate, toks pat naudingas dalykas kaip duodanti Allowed, jei jūsų kodas ją praleidžia. Globalioji AnalyzePadesSignatureRevisions ima bet kokį TStream ir skaito jį nuo 0 pozicijos – tinka įkėlimo doroklėms, kurioms dokumento atvaizduoti nereikia
uses
Classes, SysUtils, TypInfo, FPdfPades;
const
// Dubliuoti apibrėžimai užfiksuojami nežeminant Status
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 ir prasSuspicious – atmetimai, o ne įspėjimai
Reason := 'revision status ' +
GetEnumName(TypeInfo(TPadesRevisionAnalysisStatus), Ord(Report.Status));
end;
end;
Dvi ribos verta pasakytos atviro tekstu. TPadesRevisionAnalysisReport apie CMS vientisumą ar liudijimų pasitikėjimą nesako nieko, tad šie vartai stovi šalia kriptografinės ir pasitikėjimo patikros, o ne jos vietoje. Ir teisingas nuosavybės grafas P=3 kiekvienam darbui saugiu nepadaro. P=3 anotacijas leidžia iš tikrųjų, o anotacija su nepermatoma išvaizda gali uždengti pasirašytą tekstą neliesdama nė vieno turinio srauto. Jei jūsų sertifikuoti dokumentai yra sutartys, o ne peržiūros kopijos, arba sertifikuokite su P=2, arba leistus anotacijų pakeitimus nukreipkite žmogui, kaip šiame pagalbininke:
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;
Parašo revizijų audito kontrolinis sąrašas
Naudokite šį sąrašą, kad patikrintumėte, ar jūsų patikros grandinė buvo atvira, ir ar dabar ji kloja saugiai:
- PDFium Component variantai iki v3.126.2 galėjo pranešti
prasAllowedpuslapio turinio redagavimams DocMDP P=3 dokumentuose; paleiskiteTPdf.AnalyzeSignatureRevisionsdar kartą ant sertifikuotų P=3 failų, priimtų senesnių variantų - Dar kartą patikrinkite P=3 dokumentus su FieldMDP užraktais, kur lauko reikšmė ir anotacija gali dalintis netiesioginiu objektu
- Priimkite tik
prasNoLaterChangesirprasAllowed;prasIndeterminateirprasSuspiciouslaikykite nepasitikėjimo reikšmėmis - Tikrinkite
Report.Riskstaip pat kaipReport.Status, nesprrDuplicateObjectDefinitionpats savaime būsenos nekeičia - Tuščio
Changesmasyvo nelaikykite švariu rezultatu, kai parašo būsena Indeterminate; žlugęs rolių kūrimas nepraneša jokių pakeitimų - Vien
prrFieldMdpUnresolvedremdamiesi FieldMDP problemų negausite, nes bendros anotacijos atvejis iškyla tik per sprendimą ir būseną - Nuspręskite, ar jūsų darbe leistini anotacijų pakeitimai po P=3 reikalauja žmogaus peržiūros
- Analizuokite originalaus failo baitus;
SaveAsperrašytas dokumentas revizijų grandinės daugiau neturi
Revizijų analizė – vienas parašo patikros sluoksnis. Suporuokite ją su PDF skaitmeninių parašų ir PAdES lygių apžiūra žodynui ir baseline lygiui, ir su platesniu PDF saugumo rizikų auditu dėl JavaScript, paleidimo veiksmų ir įdėtųjų failų. TPdf.AnalyzeSignatureRevisions, AnalyzePadesSignatureRevisions ir čia aprašytas nuosavybę atsimenantis rolių grafas keliauja su PDFium Component Delphi, C++Builder ir Lazarus