Delovna miza, ki preverjanje skladnosti veriži z digitalnim podpisovanjem, mora uskladiti štiri korake, prav v tem vrstnem redu, in jih ves čas ohranjati vezane na en sam nabor bajtov. Izvede predpregled PDF/A ali PDF/UA. Uveljavi popravke, ki jih terjajo ugotovitve, in shrani popravljeno revizijo. Podpiše prav to revizijo. Nato podpisano datoteko prebere nazaj in potrdi, da jo podpis res pokriva. Vrstni red ni kozmetičen. Če izpustite branje nazaj, zaupate lastni poti pisanja; če predpregled teče nad napačno revizijo, vaše poročilo o skladnosti opisuje datoteko, ki je nikoli niste odpremili
Del, ki ga doma zgrajeni cevovodi najpogosteje zgrešijo, je šiv med preverjanjem in podpisovanjem. Če ju poganjate kot dve ločeni orodji s prehodom sanacije vmes, nastanejo vsaj tri različne revizije datoteke, vsaka s svojimi bajti. Poročilo predpregleda, ki ga izročite revizorju, opisuje eno od njih. Podpis zamrzne drugo. Nič v datoteki ne pove, da gre za isto revizijo, pogosto pa to niti ne drži. PDF Library for Delphi, razvijalska knjižnica losLab PDF za Delphi in C++Builder, predpregled in podpisovanje PAdES postavi za en sam fasadni razred, tako da lahko celotno zaporedje živi v enem procesu, ki nikoli ne izgubi sledi o tem, o katerih bajtih govori. Vsak spodnji klic v knjižnici obstaja danes, prav tako vsaka past, zabeležena ob njem
Tri revizije enega dokumenta in kako nastane vrzel
Preštejte shranitve. Izvirnik pride od zgoraj po toku. Prehod sanacije ga naloži, vklopi način skladnosti in zapiše popravljeno revizijo. Prehod podpisovanja doda podpis kot inkrementalno posodobitev, kar je tretji zapis. Tri shranitve, tri razporeditve bajtov, poročilo predpregleda pa ne pomeni nič, dokler ne pove, katero od teh treh pokriva. SHA-256 datoteke, zabeležen ob vsakem teku predpregleda in vsakem podpisu, je poceni sidro, s katerim dokažete, da je preverjena revizija tudi podpisana revizija
Eno vedenje knjižnice to disciplino še zaostri. Popravki skladnosti, zahtevani prek SetPDFAMode ali SetPDFUAMode, ne začnejo veljati ob klicu. Uveljavijo se med shranjevanjem. Samodejni popravki, kot sta vsiljevanje zastavic za tiskanje pripisov ali dodelitev vrstnega reda tabulatorja za PDF/UA, pristanejo v izhodni datoteki in nikjer drugje, zato vam preverjanje nad dokumentom, ki ste ga pravkar "popravili" v pomnilniku, ne pove nič o bajtih, ki gredo k podpisniku. Najprej shranite, nato izvedite predpregled shranjene datoteke. Stanje v pomnilniku je osnutek; resnična je samo datoteka na disku
Predpregled z diska in ničla, ki pomeni dvoje
Ploska vstopna točka predpregleda je CheckFileCompliance(FileName, Password, ComplianceTest, Options). Test 1 izbere PDF/A (ISO 19005), test 2 izbere PDF/UA (ISO 14289). Datoteko odpre prek pretočnega bralnika knjižnice, zato je ni treba prej naložiti s LoadFromFile, vrne pa ročico seznama nizov z eno ugotovitvijo na vnos:
var
PDF: TPDFlib;
ListID, I: Integer;
begin
PDF := TPDFlib.Create;
try
ListID := PDF.CheckFileCompliance('invoice-fixed.pdf', '', 1, 0); // 1 = PDF/A
if ListID = 0 then
begin
if PDF.LastErrorCode <> 0 then
raise Exception.Create('Preflight could not read the file')
else
Writeln('No PDF/A findings');
end
else
begin
for I := 0 to PDF.GetStringListCount(ListID) - 1 do
Writeln(PDF.GetStringListItem(ListID, I));
PDF.ReleaseStringList(ListID);
end;
finally
PDF.Free;
end;
end;
Past tiči v vrnjeni vrednosti in je take vrste, ki prestane vsak preizkus po srečni poti. Ničla pomeni "ni ugotovitev". Ničla pomeni tudi "datoteke ni bilo mogoče odpreti", saj izvedba vrne 0 vselej, kadar se seznam rezultatov vrne prazen, tudi ob napaki pri branju. Delovna miza, ki 0 bere kot zeleno luč, bo veselo odobrila datoteko, ki jo je zaklenil kak drug proces. Sparitev klica z LastErrorCode, kot zgoraj, je tisto, kar oba primera loči. Preverjevalnik datoteko odpre tudi z načinom souporabe, ki prepoveduje pisanje, zato če vaš korak sanacije še vedno drži ročico za pisanje, predpregled odpove iz razloga, ki nima nič skupnega s skladnostjo in vse s tokom, ki ste ga pozabili sprostiti
Kadar mora ugotovitve prebrati človek in ne cevovod, jih CreatePreflightReport izriše kot berljivo poročilo. ComparePreflightReports primerja dva teka, kar je urejen način, da pokažete, da je sanacija odpravila izvirne ugotovitve, ne da bi tiho vpeljala nove
Podpisovanje preverjene revizije s SignProcess
Ko shranjena revizija prestane predpregled in je njena zgoščena vrednost zabeležena, podpišite prav to datoteko in nobene druge. API SignProcess se bere kot graditelj. Odprite ročico procesa, jo nastavite vrstico za vrstico, potrdite, nato preberite vrnjeno kodo rezultata
ProcessID := PDF.NewSignProcessFromFile('invoice-fixed.pdf', '');
if ProcessID = 0 then
raise Exception.Create('Cannot open source for signing');
PDF.SetSignProcessField(ProcessID, 'ApprovalSig');
PDF.SetSignProcessPFXFromFile(ProcessID, 'company.pfx', PfxPassword);
PDF.SetSignProcessInfo(ProcessID, 'Invoice approval', 'Berlin', 'billing@example.com');
PDF.SetSignProcessCustomSubFilter(ProcessID, 'ETSI.CAdES.detached'); // izhodišče PAdES
PDF.SetSignProcessDigestAlgorithm(ProcessID, 2); // SHA-256
PDF.SetSignProcessReserveContentsBytes(ProcessID, 8192); // prostor za poznejši časovni žig
PDF.EndSignProcessToFile(ProcessID, 'invoice-signed.pdf');
if PDF.GetSignProcessResult(ProcessID) <> 1 then
Writeln('Sign failed, code ', PDF.GetSignProcessResult(ProcessID));
PDF.ReleaseSignProcess(ProcessID);
Dve vrstici v tem zaporedju nosita več teže, kot je videti. SetSignProcessCustomSubFilter z ETSI.CAdES.detached izbere podpis PAdES, kakor ga profilira ETSI EN 319 142-1, namesto podedovane družine adbe.pkcs7.detached, in prav v tem je razlika med podpisom, ki ga evropski preverjevalnik sprejme, in takim, ki ga označi. SetSignProcessReserveContentsBytes podloži rezervirano mesto /Contents, velikost, ki jo izberete tukaj, pa je odločitev o prihodnosti: če bo podpisu kdaj sledil časovni žig, se mora povečan CMS zmestiti v prostor, ki ga rezervirate zdaj, saj rezervirano mesto pozneje ne more zrasti brez ponovnega podpisa vsega skupaj. Rezervirajte radodarno in izgubite nekaj kilobajtov. Rezervirajte pretesno in korak časovnega žiga bo čez mesece odpovedal s prekoračitvijo, ki jo boste s to eno vrstico težko povezali
GetSignProcessResult odgovori s kodo in ne z logično vrednostjo, kode pa je vredno ohraniti. 1 je uspeh. 4 je napačno geslo PDF, 7 napačno geslo potrdila, 9 je PFX brez zasebnega ključa, 11 pa napaka med uveljavljanjem podpisa. Če jih stisnete v resnično ali neresnično, zavržete edini podatek, ki primer podpore z napačnim geslom loči od primera s ključem brez zasebnega dela. Zabeležite celo število
Branje nazaj: presoja datoteke, ki ste jo pravkar izdelali
Nobena delovna miza ne bi smela zaupati poti, ki je zapisala datoteko, ki jo namerava potrditi. Presojevalni razred TPDFlibSignDoc znova odpre podpisani izhod in vnose slovarja podpisa prebere naravnost z diska:
var
Doc: TPDFlibSignDoc;
Names: TStringList;
FS: TFileStream;
I: Integer;
SourceSize, RangeStart, GapStart, TailStart, TailLen: Int64;
begin
// Velikost zajemite pred Open: presojevalni objekt drži zaklep souporabe nad datoteko
FS := TFileStream.Create('invoice-signed.pdf', fmOpenRead or fmShareDenyNone);
SourceSize := FS.Size;
FS.Free;
Doc := TPDFlibSignDoc.Create;
Names := TStringList.Create;
try
if not Doc.Open('invoice-signed.pdf', '', False) then Exit;
Doc.GetSignatureFieldNames(Names);
for I := 0 to Names.Count - 1 do
if Doc.GetSignatureValueObjNum(Names[I]) > 0 then // > 0 pomeni, da je polje podpisano
begin
RangeStart := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 11)));
GapStart := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 12)));
TailStart := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 13)));
TailLen := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 14)));
if (RangeStart = 0) and (TailStart + TailLen = SourceSize) then
Writeln(Names[I], ': signature covers the file to EOF')
else
Writeln(Names[I], ': earlier revision, or unusual ByteRange layout');
end;
Doc.Close;
finally
Names.Free;
Doc.Free;
end;
end;
Argumenti ValueKey se preslikajo na vnose slovarja. Ključ 0 vrne surovi CMS iz /Contents, ključa 2 in 3 imeni /Filter in /SubFilter, ključi od 11 do 14 pa štiri števila ByteRange. Besedilne vrednosti pridejo namesto tega prek GetSignatureTextValueByName: ključ 0 je zatrjevani čas podpisa, ključ 5 pa navadni Sig loči od DocTimeStamp, kar postane pomembno, brž ko dokument nosi oba
Zajem velikosti datoteke na vrhu tega primera je nosilen in ne zgolj pospravljanje. TPDFlibSignDoc.Open datoteko vso svojo življenjsko dobo drži pod omejevalnim zaklepom souporabe, zato mora vse, kar potrebuje surove bajte (zgoščevanje podpisanega obsega, ponovni izračun povzetka CMS), datoteko prebrati, preden je Open klican. Demonstracija SigningWorkbench iz knjižnice prav zaradi tega najprej prebere celotno datoteko v pomnilnik, delovna miza, ki vrstni red prezre, pa odpoveduje občasno, na tistem računalniku, ki pač izgubi tekmo
Aritmetika ByteRange, ki dokaže pokritost
Zdrava datoteka z enim podpisom ima ByteRange oblike [0 a b c]: pokritost se začne pri odmiku 0, preskoči šestnajstiško rezervirano mesto /Contents med a in b, nato se nadaljuje skozi bajt b+c. Kadar je b+c enak velikosti datoteke, podpis pokriva vse do konca datoteke, kar je izid, ki ga želite. Kadar zaostane, je nekdo po zapisu podpisa dodal inkrementalno posodobitev. To je po ISO 32000-1§12.8 povsem legitimno, saj poznejša izpolnjevanja obrazcev, drugi podpis in slovar DSS pridejo prav na ta način. Prav tako pa je to dejstvo, ki naj ga sled presoje zabeleži ob podpisovanju, namesto da bi ga pod pritiskom rekonstruirala med sporom
Med to aritmetiko pazite na širino celega števila. Ploski API prek GetSignProcessByteRange vrne 32-bitni Integer, osnovne vrednosti pa so Int64, zato ploski dostopnik pri datoteki čez 2 GB tiho odreže vrh. Sezite po TPDFlibSigner.GetByteRange iz razredne plasti, ki vrne Int64, ali vrednosti razčlenite iz GetSignatureValueByName, kakor to počne presojevalna koda zgoraj
Kaj knjižnica prepusti vam
Dve meji je bolje spoznati pri načrtovanju kot v zadnjem sprintu. Ploski API TPDFlib nima prav nobenega ovoja za preverjanje podpisa. Kriptografsko preverjanje živi eno plast nižje, v TPDFlibSignatureVerifier, katerega VerifySignature odgovori veljavno, neveljavno ali neznano. Prav tako ni vgrajenega odjemalca HTTP za overitelje časovnih žigov po RFC 3161. Knjižnica izračuna zgoščeno vrednost za oddajo in po prejemu žetona znova vgradi razširjeni CMS, omrežni obhod do TSA pa morate napisati sami. Oboje je preprosto oviti in zares neprijetno pogrešati teden dni pred izdajo, zato ju vključite v načrt že v prvi skici
Eno vprašanje o skladnosti je vredno razčistiti naravnost, ker odloča, kam sodi zadnja kontrolna točka: ali dodajanje podpisa pokvari PDF/A? Samo po sebi ne. Podpis pride kot inkrementalna posodobitev, ISO 19005-2 in naprej pa podpisane dokumente izrecno dopušča. Zatik je videz podpisa, ki igra po istih pravilih kot vsaka druga vsebina strani, vključno z vdelanimi pisavami in prepovedjo barv, odvisnih od naprave. Zadnja kontrolna točka v delovni mizi je torej še en tek predpregleda, tokrat nad podpisanim izhodom. CheckFileCompliance obravnavajte kot hitro preverjanje znotraj cevovoda in kandidate za izdajo vseeno preverite z neodvisnim orodjem, kot je veraPDF, saj preverjevalniki uveljavljajo prekrivajoče se, a ne enake nabore pravil; kadar se ne strinjata, besedilo ugotovitve običajno navede člen, ki ga je treba prebrati
Iz vsega tega izpade ena točka glede zaporedja. Podpisovanje in časovno žigosanje nista en sam prehod: najprej se zapiše izhodiščni podpis, nato ločen proces časovnega žiga razširi CMS znotraj rezerviranega prostora /Contents, in prav zato je vrstica z rezervacijo bajtov prej nosila toliko teže. Za plasti časovnega žiga in dolgoročne veljavnosti, ki gradijo na tej delovni mizi, vodnik po podpisovanju in preverjanju PAdES podpis pelje od izhodišča do B-LT, polovica o predpregledu pa gre globlje v vodniku po predpregledu PDF/A in PDF/UA. Celotna dokumentacija API in preizkusni prenosi so na strani izdelka PDF Library for Delphi