Pracovné prostredie, ktoré spája overovanie zhody s digitálnym podpisovaním, musí koordinovať štyri kroky v presnom poradí a udržiavať ich prepojené s jedinou sadou bajtov po celú dobu. Vykoná predbežnú kontrolu PDF/A alebo PDF/UA. Použije opravy vyžadované zisteniami a uloží opravenú revíziu. Podpíše presne túto revíziu. Následne prečíta podpísaný súbor späť a potvrdí, že podpis ho skutočne pokrýva. Poradie nie je náhodné. Vynechajte spätné čítanie a budete dôverovať vlastnej ceste zápisu; spustite predbežnú kontrolu nad nesprávnou revíziou a vaša správa o zhode bude popisovať súbor, ktorý ste nikdy neodoslali.
Časť, ktorú väčšina interne vyvinutých riešení robí nesprávne, je prechod medzi validáciou a podpisovaním. Ak ich spustíte ako dva samostatné nástroje s opravným krokom medzi nimi, vzniknú najmenej tri odlišné revízie súboru, každá s vlastnými bajtmi. Správa o predbežnej kontrole, ktorú odovzdáte audítorovi, popisuje jednu z nich. Podpis zmrazí inú. Nič v súbore nepotvrdzuje, že ide o rovnakú revíziu, a často to tak ani nie je. PDFlibPas, knižnica losLab PDF Developer Library pre Delphi a C++Builder, umiestňuje predbežnú kontrolu a podpisovanie PAdES pod jednu triedu fasády, takže celý proces môže prebiehať v jedinom procese, ktorý nikdy nestratí prehľad o tom, o ktorých bajtoch hovorí. Každé volanie uvedené nižšie je dnes súčasťou knižnice, rovnako ako každé úskalie uvedené vedľa neho.
Tri revízie jedného dokumentu a ako vzniká rozpor
Spočítajte zápisy. Originál prichádza zo vstupu. Opravný krok ho načíta, zapne režim zhody a zapíše opravenú revíziu. Podpisový krok pripojí podpis ako prírastkovú aktualizáciu, čo predstavuje tretí zápis. Tri uloženia, tri rozloženia bajtov a správa o predbežnej kontrole neznamenajú nič, ak nešpecifikujú, ktorú z týchto troch revízií pokrývajú. Hodnota SHA-256 súboru, zaznamenaná vedľa každého spustenia kontroly a každého podpisu, je jednoduchým kotviacim bodom, ktorým dokážete, že overená revízia je tá istá, ktorú ste podpísali.
Jedno zo správaní knižnice toto pravidlo ešte viac sprísňuje. Opravy zhody požadované prostredníctvom SetPDFAMode alebo SetPDFUAMode nevstupujú do platnosti pri ich volaní. Uplatňujú sa až počas ukladania. Automatické opravy, ako napríklad vynútenie príznakov tlače anotácií alebo priradenie poradia tabulátorov PDF/UA, sa prejavia len vo výstupnom súbore. Kontrola spustená nad dokumentom, ktorý ste práve „opravili“ v pamäti, vám nepovie nič o bajtoch smerujúcich k podpisu. Najprv uložte, potom vykonajte kontrolu uloženého súboru. Stav v pamäti je len koncept; reálny je iba súbor na disku.
Predbežná kontrola z disku a nula, ktorá znamená dve veci
Priamym vstupným bodom predbežnej kontroly je CheckFileCompliance(FileName, Password, ComplianceTest, Options). Test 1 volí PDF/A (ISO 19005), test 2 volí PDF/UA (ISO 14289). Otvára súbor prostredníctvom streamovacej čítačky knižnice, takže ho netreba vopred načítavať pomocou LoadFromFile, a vracia identifikátor zoznamu reťazcov s jedným nálezom na položku:
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;
Úskalie spočíva v návratovej hodnote a je to ten typ správania, ktorý úspešne prejde každým základným testom. Nula znamená „žiadne nálezy“. Nula však znamená aj „súbor sa nepodarilo otvoriť“, pretože implementácia vracia hodnotu 0 vždy, keď je výsledný zoznam prázdny, vrátane zlyhania čítania. Systém, ktorý interpretuje nulu ako zelenú, bez problémov schváli súbor, ktorý je uzamknutý iným procesom. Spojenie volania s LastErrorCode, ako je uvedené vyššie, tieto dva prípady odlišuje. Kontrolný nástroj otvára súbor v režime zdieľania s odmietnutím zápisu (deny-write), takže ak váš opravný krok stále drží otvorený stream na zápis, predbežná kontrola zlyhá z dôvodu, ktorý nesúvisí so zhodou, ale so streamom, ktorý ste zabudli uvoľniť.
Keď zistenia potrebuje čítať človek a nie automatizovaný systém, CreatePreflightReport vykreslí ich do čitateľného formátu. ComparePreflightReports porovnáva dva behy kontroly, čo je elegantný spôsob, ako ukázať, že opravný krok odstránil pôvodné nedostatky bez toho, aby potichu priniesol nové.
Podpisovanie skontrolovanej revízie pomocou SignProcess
Keď uložená revízia prejde kontrolou a jej odtlačok je zaznamenaný, podpíšte presne tento súbor a žiadny iný. API SignProcess funguje ako builder. Otvorte popisovač procesu, nakonfigurujte ho krok za krokom, potvrďte a prečítajte kód výsledku.
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);
Dva riadky v tejto sekvencii majú väčší význam, než sa na prvý pohľad zdá. SetSignProcessCustomSubFilter s hodnotou ETSI.CAdES.detached zvolí podpis PAdES podľa profilu ETSI EN 319 142-1 namiesto staršieho formátu adbe.pkcs7.detached, čo predstavuje rozdiel medzi podpisom, ktorý európsky validačný nástroj prijme, a podpisom, ktorý označí ako neplatný. SetSignProcessReserveContentsBytes vymedzuje miesto pre položku /Contents a veľkosť, ktorú tu zvolíte, je rozhodnutím do budúcnosti: ak má neskôr nasledovať časová pečiatka podpisu, zväčšená štruktúra CMS sa musí zmestiť do priestoru, ktorý rezervujete teraz, pretože placeholder sa neskôr nedá zväčšiť bez opätovného podpísania celého dokumentu. Rezervujte veľkoryso a stratíte len pár kilobajtov. Ak rezervujete príliš málo, krok pridania časovej pečiatky o niekoľko mesiacov zlyhá na pretečení priestoru, ktoré budete len ťažko spájať s týmto jedným riadkom.
Metóda GetSignProcessResult vracia číselný kód, nie hodnotu Boolean, a tieto kódy majú svoju hodnotu. 1 znamená úspech. 4 je nesprávne heslo k PDF, 7 nesprávne heslo k certifikátu, 9 súbor PFX bez privátneho kľúča, 11 zlyhanie počas aplikácie podpisu. Ak tieto stavy zjednodušíte na true/false, prídete o informácie, ktoré vám pomôžu odlišuje nesprávne zadanie hesla od chýbajúceho privátneho kľúča. Zaznamenávajte celé číslo.
Spätné čítanie: audit práve vytvoreného súboru
Žiadne vývojové prostredie by nemalo slepo dôverovať ceste, ktorá zapísala súbor, ktorý sa chystá certifikovať. Auditná trieda TPDFlibSignDoc opätovne otvorí podpísaný výstup a prečíta položky slovníka podpisov priamo z disku:
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;
Argumenty ValueKey sa mapujú priamo na položky slovníka. Kľúč 0 vracia surový CMS z /Contents, kľúče 2 a 3 názvy pre /Filter a /SubFilter a kľúče 11 až 14 štyri čísla ByteRange. Textové hodnoty sa namiesto toho získavajú cez GetSignatureTextValueByName: kľúč 0 je deklarovaný čas podpísania a kľúč 5 odlišuje bežný Sig od DocTimeStamp, čo je dôležité, ak dokument obsahuje oboje.
Zachytenie veľkosti súboru na začiatku tohto príkladu je kľúčové, nejde len o kozmetickú úpravu. Metóda TPDFlibSignDoc.Open drží súbor pod reštriktívnym zámkom zdieľania po celú dobu svojej životnosti, takže čokoľvek, čo potrebuje surové bajty (hashing podpísaného rozsahu, prepočet CMS digest), musí súbor načítať pred volaním Open. Vlastné demo knižnice SigningWorkbench z tohto dôvodu načítava celý súbor najprv do pamäte; systém, ktorý toto poradie ignoruje, bude občas zlyhávať v závislosti od toho, ktorý proces vyhrá prístup k súboru.
Matematika ByteRange, ktorá dokazuje pokrytie
Správny súbor s jedným podpisom má ByteRange vo formáte [0 a b c]: pokrytie začína na offsete 0, preskočí hexadecimálny /Contents placeholder medzi hodnotami a a b, a pokračuje cez bajt b+c. Keď sa hodnota b+c rovná veľkosti súboru, podpis pokrýva celý súbor až po jeho koniec, čo je požadovaný stav. Ak je hodnota nižšia, niekto po zapísaní podpisu pripojil prírastkovú aktualizáciu. To je podľa normy ISO 32000-1§12.8 plne povolené, keďže neskoršie vypĺňanie formulárov, druhý podpis a slovník DSS sa pridávajú presne takto. Je to však presne ten fakt, ktorý by mal audit zaznamenať v čase podpísania, a nerekonštruovať ho neskôr pod tlakom počas sporu.
Pri tejto matematike si dajte pozor na šírku celého čísla. Funkcia GetSignProcessByteRange plochého API vracia 32-bitový Integer, ale interné hodnoty sú typu Int64, takže pri súboroch nad 2 GB plochý prístup hodnotu potichu oreže. Použite triedu TPDFlibSigner.GetByteRange, ktorá vracia Int64, alebo analyzujte hodnoty z GetSignatureValueByName tak, ako to robí auditný kód vyššie.
Čo knižnica ponecháva na vás
Dva limity je lepšie poznať už vo fáze návrhu než počas záverečného šprintu. Ploché rozhranie TPDFlib API neobsahuje žiadny obal pre overenie podpisu. Kryptografické overenie sa nachádza o úroveň nižšie v TPDFlibSignatureVerifier, ktorého metóda VerifySignature vracia platný, neplatný alebo neznámy výsledok. Knižnica taktiež neobsahuje vstavaného klienta HTTP pre autority časových pečiatok RFC 3161. Knižnica vypočíta hash na odoslanie a po prijatí tokenu opätovne vloží upravený CMS, ale sieťovú komunikáciu s TSA musíte implementovať sami. Obe funkcie sa dajú ľahko napísať, ale ich absencia tesne pred vydaním môže byť nepríjemná, preto s nimi počítajte od prvého návrhu.
Jednu otázku ohľadom zhody je dôležité vyjasniť, pretože určuje umiestnenie posledného kroku kontroly: porušuje pridanie podpisu normu PDF/A? Samo osebe nie. Podpis sa pridáva ako prírastková aktualizácia a norma ISO 19005-2 a novšie podpísané dokumenty výslovne povoľujú. Problémom však môže byť vzhľad podpisu, ktorý podlieha rovnakým pravidlám ako akýkoľvek iný obsah stránky, vrátane vložených písiem a vylúčenia farieb závislých od zariadenia. Posledným krokom je preto ešte jedno spustenie kontroly nad podpísaným výstupom. Používajte CheckFileCompliance ako rýchly test v rámci spracovania, ale finálne verzie overte aj nezávislým nástrojom, ako je veraPDF, pretože rôzne validačné nástroje pokrývajú podobné, ale nie úplne totožné pravidlá; pri ich rozpore vám text zistení zvyčajne ukáže na konkrétny článok normy.
Z tohto celého vyplýva jeden dôležitý dôsledok pre poradie krokov. Podpisovanie a pridanie časovej pečiatky neprebiehajú v jednom kroku: najprv sa zapíše základný podpis a potom samostatný proces časovej pečiatky doplní CMS v rámci rezervovaného priestoru /Contents. Práve preto mala predchádzajúca rezervácia bajtov takú dôležitosť. Pre časovú pečiatku a vrstvy dlhodobého overovania, ktoré na tomto riešení stavajú, vás sprievodca podpisovaním a overovaním PAdES prevedie cestou od základného podpisu po B-LT, a problematika kontroly zhody je detailnejšie rozpísaná v sprievodcovi predbežnou kontrolou PDF/A a PDF/UA. Kompletná dokumentácia API a verzie na stiahnutie sú dostupné na produktovej stránke PDFlibPas.