Egy aláírás után megváltozott aláírt PDF nem automatikusan sérült. Az ISO 32000-1 megengedi az inkrementális frissítéseket egy aláírás tetején, és ezek közül csak néhány sérti meg az aláíró által beállított szabályzatot. A HotPDF Component Delphihez és C++Builderhez az AnalyzeLoadedSignatureRevisions segítségével válaszol erre a kérdésre, amely minden aláírás utáni revíziót osztályoz, és a DocMDP és FieldMDP szerint értékel. A forgatókönyv ismerős mindenki számára, aki szerződéskezelő szoftvert szállít: az ügyfél aláír egy adásvételi szerződést, elküldi, majd egy mellékletoldallal kiegészítve kapja vissza. Az olvasó egy sárga sávot mutat, amely szerint az aláírás sértetlen, de a dokumentum aláírás óta megváltozott, és senki a szobában nem tudja megmondani, hogy ez egy szokásos ellenjegyzési workflow-e, vagy valaki csendben szerkeszti az aláírt szerződést
Mi számít legális változásnak aláírás után?
Egy változás akkor legális, ha szemantikai kategóriája beleesik abba a jogosultságba, amelyet a hitelesítő aláírás deklarált. Az ISO 32000-1 §12.8.2.2 a DocMDP transzformációt /P 1, 2 vagy 3 értékkel definiálja: 1 semmilyen változást nem enged, 2 megengedi az űrlapkitöltést és az aláírást, 3 megengedi az űrlapkitöltést, az aláírást és a jegyzetelést. A HotPDF ezeket THPDFDocMDPPermission értékekként teszi elérhetővé: dmpNoChanges, dmpFormFillAndSign és dmpFormFillSignAndAnnotate, a dmpNone pedig a semmilyen DocMDP transzformációt sem hordozó vizsgálati eredmények számára van fenntartva
A kategóriák rendezettek, és ez a rendezés a teljes ellenőrzés hajtómotorja. A THPDFRevisionModificationLevel a rmlNone, rmlLongTermValidation, rmlFormFillAndSign, rmlAnnotations, rmlOther sorrendben fut, szándékosan úgy elrendezve, hogy egy nagyobb sorszám soha nem kevésbé korlátozó. Egy teljes dokumentum az aláírás utáni minden revízió közül megfigyelt maximális szintre redukálódik, és a DocMDP-összehasonlítás egyetlen egész szám tesztté válik. Egy árnyalat korán számít: a dmpNoChanges mellett az elemzés még mindig elfogadja a rmlLongTermValidation szintet. A DSS és VRI ellenőrzési anyag vagy egy dokumentumidőbélyeg hozzáadása egy hitelesített fájlhoz az aláírás karbantartása, nem a dokumentum módosítása, és ha ezt jogsértésként kezelnénk, minden létező hosszú távú archiválási workflow-t megtörne
Hogyan építi újra a HotPDF a revíziós láncot?
Strukturálisan, nem heurisztikusan. Az ISO 32000-1 §7.5.6 szerint egy inkrementális frissítés hozzáfűz egy új keresztreferencia-szakaszt, amelynek /Prev mezője az előzőre mutat, így a HotPDF a startxref-et a végéről olvassa, elemzi az ott lévő szakaszt, visszafelé követi a /Prev-et, és megismétli, a szakaszokat a legrégebbivel kezdve adva vissza. Két biztonsági korlát ül ebben a ciklusban, és mindkettőt érdemes ismerni egy meghiúsuló fájl vizsgálatakor: egy már meglátogatott eltolásra mutató /Prev egy explicit ciklusdiagnosztikával leállítja a bejárást pörgés helyett, és egy ezer revíziónál hosszabb lánc egyszerűen elutasításra kerül. Mindkettő az Analysis.Issue-ban jelenik meg, a függvény pedig False-szal tér vissza, és egyiket sem szabad elkenni, mert egy ciklikus /Prev inkább hibás vagy ellenséges fájlra utal, mint szokatlanra
Négy történelmi forma fordul elő valós dokumentumokban, és mind a négy kezelve van: a soronként elemzett hagyományos xref táblázatok, a /W és /Index mezőiken keresztül kitömörített és dekódolt keresztreferencia-folyamok, a hibrid hivatkozású fájlok, amelyek hagyományos trailerje egy /XRefStm kulcsot hordoz, amit elemez és ugyanabba a revízióba fésül a rendszer (az Office-generátor esete, amelyet a hibrid keresztreferencia-folyamokról szóló cikk tárgyal), valamint az ObjStm konténerben élő objektumok, amelyek azért számítanak, mert egy modern frissítés általában a megváltozott szótárat egy tömörített folyamba teszi, ahelyett hogy közvetlenül írná ki, ahogy azt az objektumfolyamokról és inkrementális frissítésekről szóló cikk leírja. Az aláírás rögzíti a felosztást: a /ByteRange[2] + /ByteRange[3] lesz a SignedRevisionLength, és minden szakasz ennél az eltolásnál vagy azon túl aláírás utáninak minősül. Az, hogy a bájttartomány még mindig helyesen hashel-e, külön kérdés, amelyre a VerifyLoadedSignature ad választ, ezt a PDF digitális aláírások ellenőrzéséről szóló cikk tárgyalja
Hogyan kerül besorolásra minden egyes megváltozott objektum
A besorolás objektumonként fut, majd a hivatkozások mentén terjed tovább. Minden objektumszámhoz, amelyet egy aláírás utáni szakasz érint, a HotPDF beolvassa az új törzset és azt a törzset, amilyen az aláírt pillanatképben volt; egy azonos törzs rmlNone, mert a generátorok gyakran újraírják az objektumokat anélkül, hogy megváltoztatnák őket. A felismerők szándékosan szűkek. Egy /Type /DocTimeStamp objektum, vagy egy olyan, amelynek /SubFilter mezője ETSI.RFC3161, rmlLongTermValidation, ahogy minden a katalógus /DSS fájából elérhető is; egy /Type /Sig szótár rmlFormFillAndSign. Konténerek esetén a teszt az, hogy mely kulcsok mozdultak el, nem az, hogy micsoda az objektum: a katalógus csak /DSS, /Extensions vagy /AcroForm mezőt nyerhet vagy módosíthat; az AcroForm szótár csak /Fields, /SigFlags, /NeedAppearances, /DR, /DA vagy /Q mezőt; egy oldal csak /Annots mezőt; egy mező vagy widget csak /V, /AP, /AS vagy /M mezőt. Minden, ami ezeken a halmazokon kívül esik, rmlOther-re esik vissza, és pontosan így kerül elkapásra a hozzáfűzött mellékletoldal: egy oldal hozzáadása olyan módon rendezi át az oldalfát, amelyet semmilyen engedélyezőlista nem fed le, és semmilyen mennyiségű legitim űrlapkitöltés nem hasonlít rá
Ezután a szintek terjednek tovább, minden konténer örökli a rámutató megváltozott gyermekek maximális szintjét, egészen addig ismételve, amíg a hozzárendelés stabilizálódik. Ez teszi működővé a megjelenítési folyamokat. Egy kitöltött szövegmező újraírja a /V-t, és egy friss /AP folyamra mutat, és ez a folyam önmagában egy névtelen tartalom-operátor blob felismerhető típus nélkül; mivel a mező, amelyhez tartozik, rmlFormFillAndSign, a folyam örökli ugyanazt a szintet ahelyett, hogy rmlOther-re esne vissza. Ugyanez a terjedés viszi tovább a DSS kontextust a tanúsítvány- és visszavonási folyamokra, amelyek egyébként besorolhatatlanok lennének
Miért számít jogsértésnek egy olvashatatlan objektum?
Mert az alternatíva egy olyan validátor, amelyet legyőznek azzal, hogy olyasmit írnak, amit nem ért. Három helyzet ér véget fellebbezés nélkül rmlOther-nél a HotPDF-ben: egy objektum, amelynek törzsét nem lehetett beolvasni a revízióból, egy objektum, amelyet a revízió szabadként jelöl meg, és egy objektum, amely a fenti felismerők egyikének sem felel meg. Mindegyik egy konkrét diagnosztikát rögzít a revízió Issue mezőjében, így egy kezelő láthatja, melyik objektumszám hozta a döntést
A szabadként jelölés a három közül a legélesebb. Egy aláírás utáni revízió, amely egy korábban definiált objektumot szabadként jelöl meg, tartalmat törölt egy aláírt dokumentumból, és a §12.8.2.2 szerint egyetlen jogosultsági szint sem engedi meg ezt; az objektumszámok a FreedObjectNumbers-be kerülnek, és a revízió rmlOther-re emelkedik. Az olvashatatlan objektumok ugyanazt a logikát követik, más okból. Egy validátor, amely nem tudja elemezni az objektumot, nem tud alapot adni annak ártalmatlannak nyilvánítására, és erre az őszinte válasz nem a csend. Egy szokatlan, de ártalmatlan konstrukció jogsértésként való jelentése egy emberi felülvizsgálatba kerül; a fordított hiba egy aláírt szerződést szállít ki egy észrevétlen szerkesztéssel a belsejében
A döntés kiolvasása Delphiben
A hívás rövid. Töltsük be a dokumentumot, válasszunk egy aláírás-indexet, olvassuk a rekordot; a paraméter nélküli túlterhelés újranyitja azt a fájlt, amelyből a dokumentum betöltésre került, a TStream túlterhelés pedig a hívó által megadott bájtokat veszi át, és visszaadás előtt visszaállítja a folyam pozícióját. A PolicyCompliant az az egyetlen boolean, amelyet a legtöbb hívó szeretne, három független döntést kombinálva: a jogosultsági szótárak strukturális érvényességét, a DocMDPCompliant-ot és a FieldMDPCompliant-ot. Tartsuk az összetevőket láthatóan a felhasználói felületen, ahelyett hogy összevonnánk őket, és jegyezzük meg, hogy egy DocMDP transzformáció nélküli dokumentum a DocMDPCompliant-ot True-n hagyja, mivel egy közönséges jóváhagyó aláírás nem deklarál megsérthető szabályzatot, és az összesített ModificationLevel ekkor leíró jellegű, nem pedig döntés
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;
Triázshoz általában a revíziónkénti bontásra van szükség az összesítés helyett, mert az mutatja meg, hogy a dokumentum történetében mikor romlott el valami. Az Analysis.Revisions minden bejegyzése hordozza a láncbeli indexét, a keresztreferencia-eltolást, ahol írásra került, a saját módosítási szintjét és az érintett objektumszámokat
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;
A FieldMDP külön kerül elbírálásra, és ez szándékos
Egy dokumentum megfelelhet a DocMDP-nek, mégis jogosulatlan lehet, ezért a FieldMDPCompliant önálló boolean, nem pedig a szintösszehasonlításba beolvasztva. Az ISO 32000-1 §12.8.2.4 a FieldMDP transzformációt, a §12.7.5.5 pedig a kapcsolódó /SigFieldLock bejegyzést definiálja azzal a céllal, hogy megnevezett űrlapmezőket lefagyasszon az aláírás pillanatában, még akkor is, ha a dokumentum egésze egyébként megengedi az űrlapkitöltést. Egy mező kitöltése 2-es szintű művelet; az aláíró által lezárt mező kitöltése jogsértés, szinttől függetlenül. A HotPDF a hatókört THPDFFieldLockAction-ként olvassa be, mint flaAll, flaInclude vagy flaExclude, a flaNone pedig a lezárási szabályzatot nem hordozó eredmények számára van, a neveket pedig a Permissions.FieldNames-be: a flaAll mindent lezár, a flaInclude a felsorolt neveket zárja le, a flaExclude mindent lezár, kivéve azokat. Egy részlet fontos az eredmények olvasásakor: a ChangedFieldNames-ben csak azok a mezők jelennek meg, amelyek már jelen voltak az aláírt pillanatképben, mert egy teljes egészében aláírás után létrehozott mezőnek nincs aláírt állapota, amelynek ellentmondana, és azt inkább a DocMDP útvonal kapja el
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;
Amit ez az elemzés nem árul el
Nem ellenőriz aláírást. Az AnalyzeLoadedSignatureRevisions a struktúráról és a jogosultságokról következtet; azt, hogy az aláírt bájttartomány még mindig a CMS-blobban lévő értékre hashel-e, és hogy az aláíró tanúsítványa láncolódik-e bármihez, amiben megbízunk, a VerifyLoadedSignature és a VerifyLoadedSignatureWithTrust válaszolja meg. Egy fájl lehet tökéletesen szabályzatkövető és kriptográfiailag értéktelen egyszerre, így a két ellenőrzésnek egymás mellett kell helyet kapnia bármilyen valós elfogadási kapuban. Azt sem olvassa ki a tartalomfolyamokban rejlő szándékot: egy oldal, amelynek tartalomfolyamát teljesen kicserélték, az engedélyezőlistán kívüli változásként kerül elkapásra, de az elemzés nem árulja el, hogy a csere egy fizetési összeget cserélt ki. Egy rmlOther döntés azt jelenti, hogy egy embernek meg kell néznie, nem azt, hogy csalás történt, egy megfelelő döntés pedig azt jelenti, hogy a változás egy engedélyezett kategóriába illeszkedik, nem azt, hogy a változás szándékolt volt. Amikor csak arra van szükség, amit az aláíró deklarált, a revíziós bejárás nélkül, a GetLoadedSignaturePermissions önmagában adja vissza a szabályzat szótárakat
Minden, amit itt leírtunk, natívan fut Delphiben és C++Builderben, külső aláírási szolgáltatás nélkül a folyamatban, ez teszi gyakorlativá, hogy minden bejövő dokumentumon lefusson, ne csak azokon, amelyeket valaki már eleve gyanúsnak talált. A teljes aláírás- és revíziós API, beleértve a hozzá párosuló jogosultsági és ellenőrzési metódusokat, a HotPDF Component részét képezi Delphihez és C++Builderhez