Potpisani PDF koji se promijenio nakon potpisivanja nije automatski neispravan. ISO 32000-1 dopušta prirasna ažuriranja povrh potpisa, a samo neka od njih krše politiku koju je potpisnik postavio. HotPDF Component za Delphi i C++Builder odgovara na to pitanje pomoću AnalyzeLoadedSignatureRevisions, koji klasificira svaku reviziju nakon potpisa i ocjenjuje je prema DocMDP i FieldMDP. Scenarij je poznat svakom tko isporučuje softver za ugovore: vaš klijent potpiše ugovor o kupoprodaji, pošalje ga i dobije natrag s priloženom stranicom aneksa. Čitač prikazuje žutu traku koja kaže da je potpis netaknut, ali da je dokument izmijenjen nakon potpisivanja, a nitko u sobi ne može reći je li to normalan tijek supotpisivanja ili netko tiho uređuje potpisani ugovor
Što se računa kao zakonita promjena nakon potpisivanja?
Promjena je zakonita kad njena semantička kategorija pada unutar dopuštenja koje je certificirajući potpis deklarirao. ISO 32000-1 §12.8.2.2 definira DocMDP transformaciju s vrijednošću /P od 1, 2 ili 3: 1 ne dopušta nikakve promjene, 2 dopušta popunjavanje obrasca i potpisivanje, 3 dopušta popunjavanje obrasca, potpisivanje i napomene. HotPDF ih izlaže kao vrijednosti THPDFDocMDPPermission dmpNoChanges, dmpFormFillAndSign i dmpFormFillSignAndAnnotate, s dmpNone rezerviranim za rezultate provjere koji uopće ne nose DocMDP transformaciju
Kategorije su poredane, i taj redoslijed je pogon cijele provjere. THPDFRevisionModificationLevel ide kroz rmlNone, rmlLongTermValidation, rmlFormFillAndSign, rmlAnnotations, rmlOther, namjerno raspoređene tako da veći redni broj nikad nije manje restriktivan. Cijeli dokument svodi se na maksimalnu razinu uočenu preko svake revizije nakon potpisa, a usporedba DocMDP postaje jednostavan test cijelih brojeva. Jedna nijansa bitna je rano: kod dmpNoChanges analiza i dalje prihvaća rmlLongTermValidation. Dodavanje DSS i VRI materijala potvrde ili vremenskog žiga dokumenta certificiranoj datoteci je održavanje potpisa, a ne izmjena dokumenta, i tretiranje toga kao kršenja srušilo bi svaki dugoročni arhivski tijek rada koji postoji
Kako HotPDF ponovno gradi lanac revizija?
Strukturno, ne heuristički. Prema ISO 32000-1 §7.5.6 prirasno ažuriranje dodaje novu cross-reference sekciju čiji /Prev pokazuje na prethodnu, pa HotPDF čita startxref s kraja, raščlanjuje sekciju ondje, prati /Prev unatrag i ponavlja, vraćajući sekcije od najstarije prema najnovijoj. Dva sigurnosna ograničenja sjede u toj petlji i oba je vrijedno znati kod trijaže datoteke koja ne uspijeva: /Prev koji pokazuje na već posjećen offset prekida obilazak s eksplicitnom dijagnozom ciklusa umjesto vrtnje, a lanac dulji od tisuću revizija posve se odbija. Oboje se pojavljuje u Analysis.Issue s funkcijom koja vraća False, i nijedno se ne smije prešutjeti, jer je ciklički /Prev neispravna ili neprijateljska datoteka, a ne samo neuobičajena
Četiri povijesna oblika pojavljuju se u stvarnim dokumentima i sva se četiri obrađuju: tradicionalne xref tablice raščlanjene redak po redak, cross-reference streamovi dekomprimirani i dekodirani kroz svoja polja /W i /Index, hibridne datoteke čiji tradicionalni trailer nosi ključ /XRefStm koji se raščlanjuje i spaja u istu reviziju (slučaj Office proizvođača, obrađen u članku o hibridnim cross-reference streamovima), i objekti koji žive unutar kontejnera ObjStm, koji su bitni jer moderno ažuriranje obično stavlja izmijenjeni rječnik u komprimirani stream umjesto da ga izravno piše, kako je opisano u tekstu o object streamovima i prirasnim ažuriranjima. Potpis sidri podjelu: /ByteRange[2] + /ByteRange[3] postaje SignedRevisionLength, a svaka sekcija na tom offsetu ili poslije njega je nakon potpisa. Je li raspon bajtova i dalje ispravno hashiran, posebno je pitanje, na koje odgovara VerifyLoadedSignature, obrađen u članku o provjeri digitalnih potpisa PDF-a
Kako se klasificira svaki izmijenjeni objekt
Klasifikacija ide po objektu, a zatim se širi duž referenci. Za svaki broj objekta koji sekcija nakon potpisa dotakne, HotPDF čita novo tijelo i tijelo kakvo je stajalo u potpisanom snimku; identično tijelo je rmlNone, jer proizvođači doista ponovno pišu objekte bez da ih mijenjaju. Prepoznavatelji su namjerno uski. Objekt /Type /DocTimeStamp, ili onaj čiji je /SubFilter ETSI.RFC3161, je rmlLongTermValidation, kao i sve dostupno iz stabla /DSS kataloga; rječnik /Type /Sig je rmlFormFillAndSign. Za kontejnere test je koji su se ključevi pomaknuli, a ne što objekt jest: katalog smije samo dobiti ili izmijeniti /DSS, /Extensions ili /AcroForm; rječnik AcroForm samo /Fields, /SigFlags, /NeedAppearances, /DR, /DA ili /Q; stranica samo /Annots; polje ili widget samo /V, /AP, /AS ili /M. Sve izvan tih skupova pada na rmlOther, i upravo se tako hvata dodana stranica aneksa: dodavanje stranice presložava stablo stranica na načine koje nijedna bijela lista ne pokriva, a nikakvo legitimno popunjavanje obrasca to ne nalikuje
Zatim se razine šire, pri čemu svaki kontejner nasljeđuje maksimalnu razinu izmijenjene djece na koju pokazuje, ponavljano dok se dodjela ne stabilizira. To čini da streamovi izgleda rade. Popunjeno tekstualno polje ponovno piše /V i pokazuje na svjež stream /AP, a taj stream sam za sebe je anonimna gruda operatora sadržaja bez tipa koji bi se prepoznao; jer je polje koje ga posjeduje rmlFormFillAndSign, stream nasljeđuje istu razinu umjesto da padne na rmlOther. Isto širenje prenosi kontekst DSS-a na streamove certifikata i opoziva koji bi inače bili neklasificirivi
Zašto se nečitljiv objekt računa kao kršenje?
Zato što je alternativa validator poražen pisanjem nečega što ne razumije. Tri situacije u HotPDF-u završavaju na rmlOther bez žalbe: objekt čije tijelo nije bilo moguće pročitati iz revizije, objekt koji revizija označava kao oslobođen te objekt koji ne odgovara nijednom prepoznavatelju gore. Svaka bilježi specifičnu dijagnozu u polje Issue revizije, tako da operater može vidjeti koji je broj objekta proizveo presudu
Oslobađanje je najoštrije od te tri. Revizija nakon potpisa koja označava prethodno definiran objekt kao slobodan izbrisala je sadržaj iz potpisanog dokumenta, i nijedna razina dopuštenja pod §12.8.2.2 to ne dopušta; brojevi objekata slijeću u FreedObjectNumbers, a revizija se podiže na rmlOther. Nečitljivi objekti slijede istu logiku iz drugog razloga. Validator koji ne može raščlaniti objekt nema osnove za proglašavanje bezopasnim, a iskren odgovor na to nije šutnja. Prijavljivanje neuobičajene, ali bezopasne konstrukcije kao kršenja košta ljudsku recenziju; suprotna pogreška isporučuje potpisani ugovor s neopaženom izmjenom unutra
Čitanje presude u Delphiju
Poziv je kratak. Učitajte dokument, odaberite indeks potpisa, pročitajte zapis; preopterećenje bez parametara ponovno otvara datoteku iz koje je dokument učitan, a preopterećenje TStream uzima bajtove koje dostavlja pozivatelj i vraća poziciju streama prije povratka. PolicyCompliant je jedina bulean vrijednost koju većina pozivatelja želi, kombinirajući tri neovisne odluke: strukturnu valjanost rječnika dopuštenja, DocMDPCompliant i FieldMDPCompliant. Držite komponente vidljivima u svom sučelju umjesto da ih spajate, i primijetite da dokument bez DocMDP transformacije ostavlja DocMDPCompliant na True, jer obični potpis odobrenja ne deklarira nikakvu politiku koju bi se moglo prekršiti, pa je agregirani ModificationLevel onda opisan, a ne presuda
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;
Za trijažu obično želite raščlambu po reviziji umjesto sažetka, jer ona govori kad se u povijesti dokumenta nešto pokvarilo. Svaki unos u Analysis.Revisions nosi svoj indeks u lancu, offset cross-referencea na kojem je zapisan, vlastitu razinu izmjene i uključene brojeve objekata
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 se ocjenjuje odvojeno, i to je namjerno
Dokument može zadovoljiti DocMDP, a svejedno biti nelegitiman, zbog čega je FieldMDPCompliant zasebna bulean vrijednost, a ne uklopljena u usporedbu razina. ISO 32000-1 §12.8.2.4 definira transformaciju FieldMDP, a §12.7.5.5 povezani unos /SigFieldLock, kako bi zamrznuo imenovana polja obrasca u trenutku potpisivanja čak i kad dokument u cjelini i dalje dopušta popunjavanje obrasca. Popunjavanje polja je akcija razine 2; popunjavanje polja koje je potpisnik zaključao je kršenje bez obzira na razinu. HotPDF čita opseg u THPDFFieldLockAction kao flaAll, flaInclude ili flaExclude, s flaNone za rezultate bez politike zaključavanja, a imena u Permissions.FieldNames: flaAll zaključava sve, flaInclude zaključava navedena imena, flaExclude zaključava sve osim njih. Jedan detalj bitan je pri čitanju rezultata, u tome da se u ChangedFieldNames prijavljuju samo polja koja su već bila prisutna u potpisanom snimku, jer polje stvoreno posve nakon potpisivanja nema potpisano stanje kojem bi proturječilo, i hvata ga umjesto toga put 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;
Što vam ova analiza neće reći
Ne provjerava potpis. AnalyzeLoadedSignatureRevisions rasuđuje o strukturi i dopuštenjima; hashira li se potpisani raspon bajtova i dalje na vrijednost u CMS blobu, i lanči li se certifikat potpisnika do nečega čemu vjerujete, odgovaraju VerifyLoadedSignature i VerifyLoadedSignatureWithTrust. Datoteka može biti savršeno usklađena s politikom, a kriptografski bezvrijedna, pa dvije provjere pripadaju jedna uz drugu u svakom pravom prihvatnom vratima. Također ne čita namjeru unutar sadržajnih streamova: stranica čiji je sadržajni stream posve zamijenjen hvata se kao promjena izvan bijele liste, ali analiza vam neće reći da je zamjena zamijenila iznos plaćanja. Presuda rmlOther znači da bi čovjek trebao pogledati, ne da je počinjena prijevara, a usklađena presuda znači da promjena odgovara dopuštenoj kategoriji, ne da je promjena bila željena. Kad je jedino što trebate ono što je potpisnik deklarirao, bez obilaska revizija, GetLoadedSignaturePermissions sam vraća rječnike politike
Sve opisano ovdje izvodi se izvorno u Delphiju i C++Builderu bez vanjskog servisa za potpisivanje u petlji, što čini praktičnim pokretanje na svakom dolaznom dokumentu, a ne samo na onima za koje je netko već posumnjao. Puni API potpisa i revizija, uključujući metode dopuštenja i provjere s kojima se sparuje, dio je HotPDF Component za Delphi i C++Builder