Radno okruženje koje povezuje provjeru usklađenosti s digitalnim potpisivanjem mora koordinirati četiri koraka, ovim redoslijedom, i držati ih povezanim s jednim skupom bajtova cijelim putem. Pokreće PDF/A ili PDF/UA prethodnu provjeru (preflight). Primjenjuje sve popravke koje nalazi zahtijevaju i sprema ispravljenu reviziju. Potpisuje upravo tu reviziju. Zatim ponovno čita potpisanu datoteku i potvrđuje da je potpis stvarno pokriva. Redoslijed nije estetski. Preskočite ponovno čitanje i vjerovat ćete vlastitom putu pisanja; dopustite da se prethodna provjera pokrene protiv pogrešne revizije i vaše izvješće o usklađenosti opisivat će datoteku koju nikada niste isporučili.
Dio u kojem većina prilagođenih cjevovoda griješi je spoj između validacije i potpisivanja. Pokrenite ih kao dva zasebna alata s prolazom za popravak između njih i nastat će najmanje tri različite revizije datoteke, svaka sa svojim bajtovima. Izvješće o provjeri koje predajete revizoru opisuje jedno od njih. Potpis zamrzava drugo. Ništa u datoteci ne navodi da se radi o istoj reviziji, a često i nisu. PDFlibPas, losLab PDF Developer Library za Delphi i C++Builder, stavlja prethodnu provjeru i PAdES potpisivanje iza jedne klase fasade, tako da cijeli niz može živjeti u jednom procesu koji nikada ne gubi trag o kojim bajtovima govori. Svaki poziv u nastavku danas postoji u knjižnici, kao i svaka zamka navedena uz njega.
Tri revizije jednog dokumenta i kako nastaje jaz
Prebrojite spremanja. Izvornik dolazi s gornjeg toka. Prolaz za popravak ga učitava, uključuje način rada za usklađenost i zapisuje ispravljenu reviziju. Prolaz za potpisivanje dodaje potpis kao inkrementalno ažuriranje, što je treće pisanje. Tri spremanja, tri rasporeda bajtova, a izvješće o provjeri ne znači ništa osim ako ne navodi koje od tri pokriva. SHA-256 datoteke, zabilježen uz svako pokretanje provjere i svaki potpis, je jeftino sidro koje vam omogućuje da dokažete da je revizija koju ste provjerili upravo ona koju ste potpisali.
Jedno ponašanje knjižnice dodatno pooštrava tu disciplinu. Ispravci usklađenosti zatraženi putem SetPDFAMode ili SetPDFUAMode ne stupaju na snagu kada ih pozovete. Primjenjuju se tijekom spremanja. Automatski popravci poput prisiljavanja zastavica za ispis bilješki ili dodjele PDF/UA redoslijeda tabulatora završavaju u izlaznoj datoteci i nigdje drugdje, pa provjera pokrenuta protiv dokumenta koji ste upravo "popravili" u memoriji ne govori ništa o bajtovima koji idu prema potpisniku. Prvo spremite, a zatim pokrenite provjeru spremljene datoteke. Stanje u memoriji je skica; stvarna je samo datoteka na disku.
Preflight s diska i nula koja znači dvije stvari
Ravno polazište za prethodnu provjeru je CheckFileCompliance(FileName, Password, ComplianceTest, Options). Test 1 odabire PDF/A (ISO 19005), test 2 odabire PDF/UA (ISO 14289). Otvara datoteku kroz čitač strujanja knjižnice, tako da nema potrebe za prvim pozivom LoadFromFile, i vraća ručku popisa nizova koja nosi jedan nalaz po unosu:
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;
Zamka se nalazi u povratnoj vrijednosti, i to je ona vrsta koja prolazi svaki sretni test. Nula znači "nema nalaza." Nula također znači "datoteka se nije mogla otvoriti," jer implementacija vraća 0 kad god se popis rezultata vrati prazan, uključujući i neuspjeh čitanja. Radno okruženje koje čita 0 kao zeleno svjetlo radosno će odobriti datoteku koju je neki drugi proces zaključao. Povezivanje poziva s LastErrorCode, kao gore, ono je što razdvaja ova dva slučaja. Provjera također otvara datoteku s načinom dijeljenja koji zabranjuje pisanje, pa ako vaš korak popravka još uvijek drži ručku pisca, prethodna provjera ne uspijeva iz razloga koji nema nikakve veze s usklađenošću, a ima sve veze s tokom koji ste zaboravili osloboditi.
Kada osoba, a ne cjevovod, treba pročitati nalaze, CreatePreflightReport ih prikazuje kao čitljivo izvješće. ComparePreflightReports uspoređuje dva pokretanja, što je uredan način da se pokaže da je popravak uklonio izvorne nalaze bez tihog uvođenja novih.
Potpisivanje provjerene revizije pomoću SignProcess
Nakon što spremljena revizija prođe prethodnu provjeru i njezin je hash zabilježen, potpišite upravo tu datoteku i nijednu drugu. SignProcess API se koristi kao graditelj. Otvorite ručku procesa, konfigurirajte je liniju po liniju, potvrdite, a zatim pročitajte povratni kôd 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'); // PAdES baseline
PDF.SetSignProcessDigestAlgorithm(ProcessID, 2); // SHA-256
PDF.SetSignProcessReserveContentsBytes(ProcessID, 8192); // room for a later timestamp
PDF.EndSignProcessToFile(ProcessID, 'invoice-signed.pdf');
if PDF.GetSignProcessResult(ProcessID) <> 1 then
Writeln('Sign failed, code ', PDF.GetSignProcessResult(ProcessID));
PDF.ReleaseSignProcess(ProcessID);
```
Dvije linije u tom nizu nose veću težinu nego što se čini. SetSignProcessCustomSubFilter s ETSI.CAdES.detached odabire PAdES potpis kako je profiliran u ETSI EN 319 142-1 umjesto zastarjele obitelji adbe.pkcs7.detached, što čini razliku između potpisa koji europski validator prihvaća i onog koji označava zastavicom. SetSignProcessReserveContentsBytes nadopunjuje rezervirano mjesto /Contents, a veličina koju ovdje odaberete je odluka o budućnosti: ako će vremenski žig potpisa ikada uslijediti, povećani CMS mora stati u prostor koji sada rezervirate, jer rezervirano mjesto ne može kasnije rasti bez ponovnog potpisivanja cijele stvari. Rezervirajte velikodušno i izgubit ćete nekoliko kilobajta. Rezervirajte preusko i korak vremenskog žiga neće uspjeti mjesecima od sada s preljevom koji ćete teško povezati s ovom jednom linijom.
GetSignProcessResult odgovara kodom, a ne boolean vrijednošću, i te kodove vrijedi sačuvati. 1 je uspjeh. 4 je pogrešna lozinka za PDF, 7 pogrešna lozinka za certifikat, 9 PFX koji ne nosi privatni ključ, 11 neuspjeh tijekom primjene potpisa. Svedite to na točno/netočno i odbacit ćete jedinu informaciju koja razlikuje slučaj podrške s pogrešnom lozinkom od slučaja ključa bez privatnog dijela. Zabilježite cijeli broj.
Čitanje natrag: revizija datoteke koju ste upravo stvorili
Nijedno radno okruženje ne bi trebalo vjerovati putu koji je zapisao datoteku koju namjerava certificirati. Klasa revizije TPDFlibSignDoc ponovno otvara potpisan izlaz i čita unose rječnika potpisa izravno s diska:
var
Doc: TPDFlibSignDoc;
Names: TStringList;
FS: TFileStream;
I: Integer;
SourceSize, RangeStart, GapStart, TailStart, TailLen: Int64;
begin
// Capture the size before Open: the audit object holds a share lock on the file
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 means the field is signed
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 mapiraju se na unose rječnika. Ključ 0 vraća sirovi CMS iz /Contents, ključevi 2 i 3 nazive /Filter i /SubFilter, a 11 do 14 četiri ByteRange broja. Tekstualne vrijednosti se umjesto toga vraćaju putem GetSignatureTextValueByName: ključ 0 je traženo vrijeme potpisivanja, a ključ 5 razlikuje obični Sig od DocTimeStamp-a, što je važno kada dokument nosi oboje.
Hvatanje veličine datoteke na vrhu tog primjera je noseće, a ne kozmetičko. TPDFlibSignDoc.Open drži datoteku pod restriktivnim zaključavanjem dijeljenja tijekom cijelog svog životnog vijeka, pa sve što treba sirove bajtove (raspršivanje potpisanog raspona, ponovno izračunavanje CMS sažetka) mora pročitati datoteku prije nego što se pozove Open. Demonstracija SigningWorkbench same knjižnice prvo čita cijelu datoteku u memoriju upravo iz tog razloga, a radno okruženje koje zanemaruje redoslijed ne uspijeva povremeno, na onom stroju koji slučajno izgubi utrku.
ByteRange aritmetika koja dokazuje pokrivenost
Zdrava datoteka s jednim potpisom ima ByteRange u obliku [0 a b c]: pokrivenost počinje na pomaku 0, preskače heksadecimalno rezervirano mjesto /Contents između a i b, a zatim se nastavlja kroz bajt b+c. Kada je b+c negativan ili prelazi veličinu datoteke, potpis pokriva sve do kraja datoteke, što je rezultat koji želite. Kada je manji, netko je dodao inkrementalno ažuriranje nakon što je potpis zapisan. To je savršeno legitimno prema ISO 32000-1§12.8, budući da kasnija popunjavanja obrazaca, drugi potpis i DSS rječnik stižu točno na ovaj način. To je također upravo činjenica koju bi revizijski trag trebao zabilježiti u trenutku potpisivanja, umjesto da je rekonstruira pod pritiskom tijekom spora.
Pazite na širinu cijelog broja dok radite ovu aritmetiku. GetSignProcessByteRange ravnog API-ja vraća 32-bitni Integer, ali osnovne vrijednosti su Int64, pa na datoteci većoj od 2 GB ravni pristupnik tiho skraćuje vrijednost. Posegnite za klasnim slojem TPDFlibSigner.GetByteRange koji vraća Int64 ili raščlanite vrijednosti iz GetSignatureValueByName na način na koji to radi gornji revizijski kod.
Što vam knjižnica prepušta
Dvije granice je bolje naučiti u vrijeme projektiranja nego u završnom sprintu. Ravni TPDFlib API uopće ne sadrži omot za provjeru potpisa. Kriptografska provjera živi jedan sloj niže, u TPDFlibSignatureVerifier, čija metoda VerifySignature odgovara s valid, invalid ili unknown. Također ne postoji ugrađeni HTTP klijent za RFC 3161 autoritete vremenskih žigova. Knjižnica izračunava hash za slanje i ponovno ugrađuje prošireni CMS nakon što se token vrati, ali mrežni krug do TSA-a je vaš za napisati. Oboje je jednostavno omotati i uistinu neugodno otkriti da nedostaje tjedan dana prije izdanja, stoga ih projektirajte od prve skice.
Jedno pitanje o usklađenosti vrijedi jasno riješiti, jer ono odlučuje kamo ide zadnji korak: krši li dodavanje potpisa PDF/A? Ne samo po sebi. Potpis dolazi kao inkrementalno ažuriranje, a ISO 19005-2 nadalje izričito dopušta potpisane dokumente. Kvaka je u izgledu potpisa, koji igra po istim pravilima kao i bilo koji drugi sadržaj stranice, uključujući ugrađene fontove i bez boja ovisnih o uređaju. Dakle, zadnja vrata u radnom okruženju su još jedno pokretanje prethodne provjere, ovaj put protiv potpisanog izlaza. Tretirajte CheckFileCompliance kao brzu provjeru unutar cjevovoda i ipak provjerite kandidate za izdanje neovisnim alatom kao što je veraPDF, budući da validatori implementiraju preklapajuće, ali ne i identične skupove pravila; kada se to dvoje ne slaže, tekst nalaza obično imenuje klauzulu koju treba pročitati.
Jedna točka redoslijeda proizlazi iz svega ovoga. Potpisivanje i dodavanje vremenskog žiga nisu jedan prolaz: prvo se piše osnovni potpis, a zatim zasebni proces vremenskog žiga proširuje CMS unutar rezerviranog prostora /Contents, što je upravo razlog zašto je ranija linija rezerviranja bajtova imala toliku težinu. Za vremenski žig i dugoročne slojeve validacije koji se grade na ovom radnom okruženju, vodič za PAdES potpisivanje i validaciju vodi potpis od osnovnog do B-LT, a dio o prethodnoj provjeri indekse provjere provodi dublje u vodiču za PDF/A i PDF/UA preflight. Cjelokupna dokumentacija API-ja i probna preuzimanja nalaze se na PDFlibPas stranici proizvoda.