Da bi izvedeli, kaj se je v PDF spremenilo, potem ko je bil podpisan, komponenta PDFium Component za Delphi in Lazarus ponuja TPdf.AnalyzeSignatureRevisions, analizator sprememb revizij po podpisu, ki iz izvirnih bajtov datoteke znova zgradi vsako inkrementalno revizijo, vsako poznejšo spremembo objekta oceni proti pravilom DocMDP in FieldMDP tega podpisa in definicije senc objektov poroča kot ločeno tveganje. Situacija, na katero cilja, je znana vsakomur, ki ravna s pogodbami: overjena obrazca gre ven, vrne se z dvema dodatnima inkrementalnima shranjevanjema in vsak podpis se še vedno potrdi. To je pričakovano, ker podpis pokriva le bajte svoje revizije. Pravo vprašanje je, ali so bila ta poznejša shranjevanja dovoljena, zeleno kljukica na podpisu pa tega ne odgovori
Zakaj API za podpise PDFium ne more pokazati, kaj se je spremenilo po podpisu?
API za podpise PDFium ne more pokazati sprememb po podpisu, ker bere le slovar podpisa: /Contents, /ByteRange, /SubFilter in vrednost dovoljenj DocMDP. PDFium nima grafa inkrementalnih revizij, ne razčlenjuje parametrov transformacije FieldMDP in ne ponuja objektne primerjave med revizijami, zato analizator v FPdfPades.pas dela neposredno na surovih bajtih. To ima praktično posledico, okoli katere se zasnujte. TPdf.AnalyzeSignatureRevisions bere bajte, shranjene ob nalaganju dokumenta, nikoli pa kopije, ustvarjene s SaveAs, ker je prepisana datoteka izgubila prav tisto strukturo revizij, ki se analizira. Če je dokument prišel iz napredovalnega vira, ki ni končal z nalaganjem, poročilo vrne SourceStatus = pvssIncomplete in Status = prasIndeterminate, namesto da bi analiziral odsekano datoteko
Znovogradnja meja revizij iz startxref, tokov xref in /Prev
Analizator meje revizij znova zgradi tako, da sledi vsakemu startxref nazaj skozi klasične tabele xref, tokove navzkrižnih sklicev, vnose /XRefStm hibridnega sklicevanja in verigo /Prev, kot je definirano za inkrementalne posodobitve v ISO 32000-1 §7.5.6 in §7.5.8. Pokrita dolžina vsakega podpisa je konec njegovega drugega razpona ByteRange, analizator pa to dolžino preslika na revizijo, v katerega razdelek xref pade. Ko se ne ujema nobena revizija, podpis dobi prrCoveredRevisionNotFound in status Indeterminate. Stanje vsakega objekta se nato ponovno predvaja do pokrite revizije in vsak poznejši vnos xref se primerja s tem stanjem. To šteje več, kot se sliši: nekateri pisci ob vsakem inkrementalnem shranjevanju ponovno zapišejo celotno tabelo xref, vnos, ki še vedno kaže na isti nespremenjen objekt, pa se preskoči, namesto da bi bil prijavljen kot sprememba. Brez te primerjave bi se povsem zakonita izpolnitev obrazca utopila v stotinah lažnih sprememb
Sence definicij so primer, ki zasluži največ pozornosti. Telo objekta, ki se pojavi znotraj bajtnega razpona poznejše revizije, a ga xref te revizije ne sklicuje, je za običajen pregledovalnik nevidno, vendar je točno tista vrsta staginga, na katero se zanašajo shadow napadi: skrito vsebino se posadi pred podpisom ali za njim in pozneje aktivira s preklopom sklica. AnalyzePadesSignatureRevisionsBytes tak objekt zabeleži kot neavtoritativno spremembo s IsAuthoritative = False, oceni ga prdSuspicious ne glede na raven dovoljenj in doda prrUnreferencedObjectDefinition k nizu tveganj. Dve sorodni tveganji pokrivata druge strukturne trike: prrDuplicateObjectDefinition se sproži, ko en razdelek xref našteje isti objekt več kot enkrat, prrSignatureObjectRedefined pa, ko poznejša revizija prenovo definira obstoječi objekt podpisa
uses
SysUtils, TypInfo, PDFium, FPdfPades;
const
ShadowTag: array[Boolean] of string = ('', ' (shadow)');
var
Pdf: TPdf;
Report: TPadesRevisionAnalysisReport;
i, j: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'contract-returned.pdf';
Pdf.Active := True;
Report := Pdf.AnalyzeSignatureRevisions;
Writeln('Revisions: ', Report.RevisionCount,
' Signatures: ', Report.SignatureCount,
' Overall: ', GetEnumName(TypeInfo(TPadesRevisionAnalysisStatus),
Ord(Report.Status)));
for i := 0 to High(Report.Signatures) do
with Report.Signatures[i] do
begin
Writeln(Format('Signature %d covers revision %d, %d later, P=%d, FieldMDP=%s',
[SignatureIndex, CoveredRevisionIndex, LaterRevisionCount,
DocMdpPermission,
GetEnumName(TypeInfo(TPadesFieldMdpAction), Ord(FieldMdpAction))]));
for j := 0 to High(Changes) do
Writeln(Format(' rev %d obj %d %s -> %s%s',
[Changes[j].RevisionIndex, Changes[j].ObjectNumber,
GetEnumName(TypeInfo(TPadesRevisionChangeKind), Ord(Changes[j].Kind)),
GetEnumName(TypeInfo(TPadesRevisionDecision), Ord(Changes[j].Decision)),
ShadowTag[not Changes[j].IsAuthoritative]]));
end;
finally
Pdf.Free;
end;
end;
Kako se DocMDP in FieldMDP uveljavljata za vsak podpis?
DocMDP in FieldMDP sta uveljavljena ločeno za vsak podpis, pri njegovi lastni pokriti reviziji, tako da lahko overitveni podpis in poznejši odobritveni podpis v isti datoteki prideta do različnih sodb o isti spremembi. Vsak poznejši objekt je najprej razvrščen v TPadesRevisionChangeKind iz svojih vnosov /Type, /Subtype in /FT ter iz vloge, ki jo igra v grafih strani, obrazca, pripombe in DSS. Karkoli nosi /JavaScript, /JS, /Launch, /OpenAction, /AA, /RichMedia ali /EmbeddedFile, postane prckActiveContent. Odločitev nato sledi ISO 32000-1 §12.8.2.2: pri P=1 je vse razen podatkov navzkrižnih sklicev in validacijskega materiala nedovoljeno; P=2 dovoljuje izpolnjevanje obrazcev in nadaljnje podpise, a zavrne spremembe pripomb; P=3 dovoljuje tudi pripombe. Vsebina strani, struktura dokumenta, metapodatki, dejavna vsebina in izbrisani objekti so nedovoljeni pod katero koli ravnijo DocMDP in ocenjeni prdSuspicious, kadar podpis sploh ne nosi DocMDP, saj odobritveni podpis formalno nič ne prepoveduje, bralec pa ne vidi več, kaj je bilo podpisano
FieldMDP, iz ISO 32000-1 §12.8.2.4, odločitev o poljih obrazca še zoži. pfmaAll zaklene vsako polje, pfmaInclude zaklene le našteta polja, pfmaExclude pa zaklene vse razen naštetih. Za uporabo Include ali Exclude analizator vsako spremenjeno polje razreši v njegovo popolnoma kvalificirano ime skozi verigo /Parent in ga primerja s seznamom zaklepov po točnem ujemanju, zato naštevajte končna imena polj in ne pričakujte, da ime starša pokrije svoje otroke. Ko se imena ne da razrešiti ali transformacija uporabi dejanje, ki ga razčlenjevalnik ne prepozna, sprememba postane prdIndeterminate in se sproži prrFieldMdpUnresolved. Odločitve na spremembo se nato strnejo z najhujšim na prvem mestu, pri čemer je Suspicious nad Disallowed, Disallowed nad Indeterminate in Indeterminate nad Allowed, tako da en senc objekt prevaga nad poljubnim številom zakonitih posodobitev polj
Zakaj se nekatere spremembe vrnejo kot Indeterminate namesto varne?
Spremembe se vrnejo kot Indeterminate vsakič, ko analizator ne more dokazati, da je sprememba dovoljena, ker mora biti v preizkusu podpisov neznano nikoli prijavljeno kot dovoljeno. En pogost primer je namesto tega obravnavan natančno: dolgoročna validacija doda /DSS in prenovo kataloga, kar bi sicer štelo kot strukturna sprememba pod P=1. Analizator odstrani /DSS in /Extensions iz starega in novega kataloga ter primerja preostanek; kadar se nič drugega ne razlikuje, se prenova obravnava kot posodobitev validacijskega materiala in je dovoljena, tako da razširitev B-LT in B-LTA ne pokvarita overitvenega podpisa. Druge vrzeli so namerno ostavljene odprte. Vnosi tipa 2 v toku navzkrižnih sklicev kažejo v stisnjene toke objektov, analizator pa ne razširja tokov objektov znotraj te varnostne meje, zato te spremembe pridejo na plan kot prckCompressedObject s prrCompressedObjectUnresolved, nedovoljene pod P=1 in sicer Indeterminate. Trdna proračuna 1024 revizij, 1.000.000 številk objektov in 2.000.000 prijavljenih sprememb proizvedeta prrResourceLimitExceeded, zlomljena veriga xref pa prrMalformedRevisionChain; oba se končata kot Indeterminate, nikoli kot uspeh
const
StructuralRisks: TPadesRevisionRisks = [prrMalformedRevisionChain,
prrDuplicateObjectDefinition, prrUnreferencedObjectDefinition,
prrSignatureObjectRedefined, prrResourceLimitExceeded];
function RevisionVerdict(const R: TPadesRevisionAnalysisReport): string;
begin
// Nekatera tveganja so zabeležena brez spremembe Status, zato jih preizkusite najprej
if R.Risks * StructuralRisks <> [] then
Exit('review: structural risk in the revision chain');
case R.Status of
prasNoLaterChanges: Result := 'accept: nothing was added after signing';
prasAllowed: Result := 'accept: every later change is permitted';
prasDisallowed: Result := 'reject: a change violates DocMDP or FieldMDP';
prasSuspicious: Result := 'reject: shadow or unconstrained content change';
prasIndeterminate: Result := 'review: the analyzer could not decide';
else
Result := 'not checked: no signatures or no original bytes';
end;
end;
Vrstni red v tej pregradi je namernen. prrDuplicateObjectDefinition se doda k nizu tveganj, ne da bi sam znižal Status, transformacija FieldMDP, ki je ni mogoče razčleniti, pa vpliva na status šele, ko se dejansko spremeni polje obrazca, zato pregrada, ki gleda samo na Status, lahko spregleda dokaze, ki jih poročilo že vsebuje. Upoštevajte tudi, česa poročilo ne trdi. TPadesRevisionAnalysisReport ne pove ničesar o tem, ali je podpis CMS kriptografsko veljaven, in ali veriga potrdila podpisovalca vodi do korena, ki mu zaupate. Analiza revizij odgovori na vprašanje, kaj se je zgodilo po podpisu, in pripada poleg strukturne in validacije zaupanja, ne namesto njiju
Zapis izvornih vrednosti in zaklepov MDP ob podpisovanju
Ista pravila se lahko zapišejo ob podpisovanju prek TPadesSignatureFieldOptions, ki je član FieldOptions obeh TPadesSignOptions in TPadesRemoteSignOptions. PDFium zna ustvariti pripomoček (widget), ne zna pa zapisati /SV, /Lock, transformacije FieldMDP ali DocMDP ali kataloga slovarja /Perms, zato lasten inkrementalni pisec PAdES komponente te objekte proizvede znotraj iste posodobitve xref kot podpis. FieldName nastavi korensko ime polja, RequiredSeedValues postane biti /Ff slovarja izvornih vrednosti (seed value), opisanega v ISO 32000-1 §12.7.4.5, Reasons, LegalAttestations in AcceptableCertificates omejijo, kaj si lahko izbere poznejši podpisovalec, LockAction z LockFields zapiše nedirektni /SigFieldLock, CertificationPermission od 1 do 3 pa spremeni podpis v overitvenega. Transformaciji DocMDP in FieldMDP gredo obe v eno tabelo /Reference na vrednosti podpisa, vsaka s /Data, ki kaže na katalog
var
Options: TPadesSignOptions;
begin
Options := TPadesSignOptions.Default;
Options.CertificateThumbprint := 'A1B2C3D4E5F60718293A4B5C6D7E8F9012345678';
Options.Reason := 'Approved for release';
Options.FieldOptions.FieldName := 'Certification';
Options.FieldOptions.CertificationPermission := 2; // le izpolnjevanje formul in podpisovanje
Options.FieldOptions.RequiredSeedValues := [psvcSubFilter, psvcDigestMethod];
Options.FieldOptions.LockAction := pfmaInclude; // zakleni le ta polja
SetLength(Options.FieldOptions.LockFields, 2);
Options.FieldOptions.LockFields[0] := 'Total';
Options.FieldOptions.LockFields[1] := 'IBAN';
if not Pdf.SignPades('contract-certified.pdf', Options) then
Writeln('Signing failed');
end;
Nekaj podrobnosti se zlahka pokvari, če to ročno sestavljate sami. Katalog /Perms /DocMDP se mora sklicevati na slovar vrednosti podpisa in ne na pripombo pripomočka (widget), pisec pa ravno zato obdrži vrednost podpisa kot svoj nedirektni objekt. Obstojeci slovar /Perms lahko že nosi pravice uporabe /UR3, zato ga pisec kopira in vstavi /DocMDP, namesto da bi ga zamenjal, v skladu s slovarjem dovoljenj v ISO 32000-1 §12.8.4. Dokument, ki že nosi /DocMDP, odkloni drug overitveni podpis s EPadesCrypto, enako neskladne možnosti: zaklep Include ali Exclude brez imen polj, zaklep All s seznamom polj, pravno potrdilo na ne-overitvenem podpisu ali pika v korenskem imenu polja. Oddaljeno podpisovanje doda še eno pravilo, ker je potrdilo za podpisovanje neznano, kadar teče PreparePadesRemoteSignature: nastavitev CertificateRequired tam zahteva izrecen seznam AcceptableCertificates, lokalno podpisovanje pa lahko pade nazaj na razrešeno potrdilo podpisovalca
Analiza revizij dopolni orodjarno podpisov, ne da bi zamenjala kateri koli njen del. Začnite z pregledovanjem podpisov PDF in ravni PAdES s komponento PDFium Component, da preberete slovar in osnovno raven, poglejte zakaj validatorji zavrnejo podpise PAdES za strukturne odpovedi, ki pridejo pred vsakim vprašanjem revizij, in vključite sodbo v širšo revizijo varnostnih tveganj PDF poleg preizkusov JavaScript in vgrajenih datotek. TPdf.AnalyzeSignatureRevisions, TPadesSignatureFieldOptions in inkrementalni pisec PAdES, prikazan tukaj, pridejo s PDFium Component za Delphi, C++Builder in Lazarus