Validácia jedného podpisu PAdES znamená kontrolu troch nezávislých vecí a zelené zaškrtnutie v prehliadači hovorí iba o tej tretej. Po prvé, pole /ByteRange musí pokrývať správne bajty: ním definované rozsahy musia zrekonštruovať presný vstup, z ktorého sa počítal CMS odtlačok (digest), pričom mimo nich nesmú zostať žiadne podpísané bajty. Po druhé, certifikát vnútri CMS sa musí zreťaziť s dôveryhodným koreňovým certifikátom a obsahovať podpísaný atribút podpisového certifikátu (signing-certificate attribute), ktorý PAdES vyžaduje. Po tretie, ak profil deklaruje časovú pečiatku, token RFC 3161 musí spájať hodnotu podpisu s okamihom pred exspiráciou certifikátu. Acrobat spája všetky tri kontroly do jednej ikony; nástroj na overovanie zhody ich však uchováva oddelene, a rovnako by mal postupovať aj kód, ktorý tieto súbory vytvára. Knižnica losLab PDF Library (PDFlibPas) vám poskytuje podpisovú stranu tohto procesu, opätovné vloženie časovej pečiatky a volania auditu na kontrolu ByteRange predtým, ako súboru vyjadríte dôveru.
Jeden rozdiel dokáže potrápiť takmer každú prvotnú implementáciu PAdES, preto stojí za to ho uviesť skôr, ako prejdeme ku kódu. Podpis zapísaný s parametrom /SubFilter /adbe.pkcs7.detached je úplne korektný podpis podľa ISO 32000-1 §12.8, ktorý Acrobat vyhodnotí ako platný. Nejde však o podpis PAdES, pretože norma ETSI EN 319 142-1 vyžaduje ETSI.CAdES.detached na každej základnej úrovni. Nástroj na overovanie zhody eIDAS prvý typ odmietne a druhý priime, aj keď je kryptografia identická. Profil je vyhlásením, ktoré dokument robí sám o sebe, a správne nastavenie tohto vyhlásenia predstavuje v PDFlibPas iba jedno volanie.
Čo mení podpis PDF na podpis PAdES
Norma ETSI EN 319 142-1 definuje štyri základné úrovne (baseline levels) nadstavby nad formátom CMS. PAdES-B-B je východiskovým bodom: podpis CAdES v podpisovom poli PDF s hodnotou SubFilter nastavenou na ETSI.CAdES.detached a podpísaným atribútom podpisového certifikátu. PAdES-B-T pridáva časovú pečiatku RFC 3161 nad hodnotu podpisu, čím dokazuje, že podpis existoval pred určitým okamihom, ktorý nemožno spätne zmeniť. PAdES-B-LT vkladá certifikáty, zoznamy CRL a odpovede OCSP potrebné na overenie do úložiska Document Security Store (DSS), takže súbor zostáva overiteľný aj po ukončení infraštruktúry vydávajúcej autority (CA). PAdES-B-LTA uzatvára túto nadstavbu časovou pečiatkou dokumentu, ktorá opätovne chráni nazhromaždené dôkazy pri oslabovaní kryptografických algoritmov.
Knižnica PDFlibPas mapuje tieto koncepty do svojho rozhrania API pre proces podpisovania. Označením profilu je SetSignProcessCustomSubFilter. Ak vaša politika vyžaduje indikáciu typu záväzku (commitment-type indication, napr. dôkaz o pôvode, schválení alebo iné ETSI identifikátory s číslami 1 až 6), použije sa SetSignProcessCommitmentType. Explicitná podpisová politika sa pripája pomocou SetSignProcessSignaturePolicy, ktorá preberá OID politiky a jej odtlačok. Jedno predvolené nastavenie si zaslúži pozornosť: ak necháte algoritmus odtlačku na voľbe auto, knižnica vyberie SHA-256 pre ETSI a podpisy adbe.pkcs7.detached a k SHA-1 sa vráti iba pri staršej ceste adbe.pkcs7.sha1. Nastavte ho však explicitne. Audítori sa pýtajú na použitý hash a explicitná hodnota v kóde sa obhajuje oveľa jednoduchšie než predvolená hodnota, ktorou by ste ju museli vyhľadávať v dokumentácii.
Vytvorenie základného podpisu
Jednoduché API (flat API) riadi podpisovanie ako jednorazový stavový stroj: otvorí proces na zdrojovom súbore, nakonfiguruje ho, zapíše výsledok do výstupného súboru a načíta výsledný kód. Nasledujúca sekvencia vytvára podpis PAdES-B-B s algoritmom SHA-256. Riadok, na ktorom najviac záleží, nemá nič spoločné so samotným podpisom. Je to zámerne naddimenzovaná rezervácia /Contents, pretože to je jediná vec, ktorú neskôr nemôžete zmeniť, ak bude do tohto podpisu potrebné pridať časovú pečiatku.
var
Pdf: TPDFlib;
SignId: Integer;
begin
Pdf := TPDFlib.Create;
try
SignId := Pdf.NewSignProcessFromFile('invoice.pdf', '');
if SignId = 0 then
raise Exception.Create('cannot open source PDF');
Pdf.SetSignProcessField(SignId, 'Sig1');
Pdf.SetSignProcessPFXFromFile(SignId, 'company.pfx', PfxPassword);
Pdf.SetSignProcessInfo(SignId, 'Approved', 'Vienna', 'billing@example.com');
Pdf.SetSignProcessCustomSubFilter(SignId, 'ETSI.CAdES.detached');
Pdf.SetSignProcessDigestAlgorithm(SignId, 2); // SHA-256
Pdf.SetSignProcessReserveContentsBytes(SignId, 8192); // room for a timestamp later
Pdf.EndSignProcessToFile(SignId, 'invoice-signed.pdf');
if Pdf.GetSignProcessResult(SignId) <> 1 then
raise Exception.CreateFmt('signing failed, code %d',
[Pdf.GetSignProcessResult(SignId)]);
Pdf.ReleaseSignProcess(SignId);
finally
Pdf.Free;
end;
end;
Funkcia NewSignProcessFromFile vracia 0, ak sa zdroj nedá vôbec otvoriť. Následne GetSignProcessResult rozlišuje stavy zlyhania, ku ktorým reálne dochádza v produkcii: 4 znamená chybné heslo k PDF, 7 chybné heslo k PFX, 9 súbor certifikátu bez privátneho kľúča, 10 nezapisovateľnú cestu k výstupu a 11 zlyhanie pri aplikovaní bajtov podpisu. Zaznamenanie číselného kódu spolu s názvom vstupného súboru premení nejasný lístok podpory na analýzu trvajúcu jednu minútu.
Pridanie časovej pečiatky RFC 3161, ktorú knižnica sama neprevezme
Knižnica PDFlibPas neobsahuje klienta pre TSA (Time Stamping Authority), čo je zámerné vymedzenie hraníc a nie nedostatok. Knižnica vypočíta hash, ktorý musí autorita časových pečiatok podpísať, a následne spätne vloží upravené CMS; prenos cez HTTP a úprava CMS medzi týmito krokmi sú na strane volajúceho. Pre toto rozdelenie existuje vážny technický dôvod. Riadenie Windows CryptoAPI, ktoré nominálne pridáva nepodpísané atribúty, konkrétne CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR, zlyháva s chybou CRYPT_E_INVALID_INDEX na odpojenej štruktúre SignedData, ktorú PAdES využíva. Rozšírené CMS preto musí pochádzať z CMS kodéra pod vašou vlastnou kontrolou. Žiadna knižnica nedokáže ticho začleniť token jedným systémovým volaním, a každá, ktorá to tvrdí, vykonáva túto operáciu niekde, kde na ňu nevidíte.
var
Pdf: TPDFlib;
StsId: Integer;
HashHex, TstDer, TsAttr, AugmentedCms: AnsiString;
begin
Pdf := TPDFlib.Create;
try
StsId := Pdf.NewPAdESSignatureTimeStampProcessFromFile('invoice-signed.pdf', '');
Pdf.SetPAdESSignatureTimeStampField(StsId, 'Sig1');
Pdf.SetPAdESSignatureTimeStampDigestAlgorithm(StsId, 2);
HashHex := Pdf.GetPAdESSignatureValueHashHex(StsId);
// both calls below are application code: an HTTP POST to your TSA,
// and a CMS re-encode that attaches the token as an unsigned attribute
TstDer := RequestTimeStampToken(HashHex);
TsAttr := Pdf.BuildPAdESSignatureTimeStampAttribute(TstDer);
AugmentedCms := AttachUnsignedAttribute(Pdf.GetPAdESSignatureCMSBytes(StsId), TsAttr);
Pdf.SetPAdESSignatureCMSBytes(StsId, AugmentedCms);
Pdf.EndPAdESSignatureTimeStampProcessToFile(StsId, 'invoice-bt.pdf');
if Pdf.GetPAdESSignatureTimeStampProcessResult(StsId) <> 1 then
raise Exception.Create('timestamp embedding failed');
Pdf.ReleasePAdESSignatureTimeStampProcess(StsId);
finally
Pdf.Free;
end;
end;
Sledujte výsledné kódy: 12 znamená, že pomenované podpisové pole neexistuje, 11 že existujúce CMS nebolo možné naformátovať a 13 že rozšírené CMS sa už nezmestí do vyhradeného miesta /Contents. Kód 13 je ten najproblematickejší, pretože jedinou nápravou je opätovné podpísanie: bežný token časovej pečiatky s jeho reťazcom certifikátov zaberá 4 až 6 KB a vyhradenie 8192 bajtov počas kroku B-B sa robí presne preto, aby v tomto kroku zostalo dostatok miesta.
Validácia začína pri ByteRange, nie pri reťazci certifikátov
Zelené zaškrtnutie v prehliadači je vyjadrením dôvery voči úložisku certifikátov daného počítača, nie štrukturálnym verdiktom o súbore. Programová validácia by mala začať na nižšej úrovni otázkou, ktorú prírastkové aktualizácie (incremental updates) robia nejednoznačnou: ktoré bajty v skutočnosti pokrýva každý podpis? Každé tu popísané vylepšenie, či už ide o druhý podpis, slovník DSS alebo časovú pečiatku dokumentu, prichádza formou prírastkovej aktualizácie a každá aktualizácia pripája bajty mimo /ByteRange predchádzajúceho podpisu. Tieto pripojené bajty sú legitímne. Validátor ich však musí klasifikovať podľa politiky úprav dokumentu, pričom úroveň DocMDP pre konkrétne pole je možné načítať pomocou GetSignatureDocMDPLevelByName.
var
Doc: TPDFlibSignDoc;
Names: TStringList;
I: Integer;
B0, B1, B2, B3, FileSize: Int64;
begin
FileSize := TFile.GetSize('invoice-bt.pdf'); // before Open: SignDoc holds a share lock
Doc := TPDFlibSignDoc.Create;
try
if not Doc.Open('invoice-bt.pdf', '', False) then
raise Exception.Create('cannot open for audit');
Names := TStringList.Create;
try
Doc.GetSignatureFieldNames(Names);
for I := 0 to Names.Count - 1 do
if Doc.GetSignatureValueObjNum(Names[I]) > 0 then // >0 means actually signed
begin
B0 := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 11)));
B1 := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 12)));
B2 := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 13)));
B3 := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 14)));
if (B0 = 0) and (B2 + B3 = FileSize) then
Writeln(Names[I], ': covers the file to EOF')
else
Writeln(Names[I], ': earlier revision, or unexpected ByteRange layout');
end;
finally
Names.Free;
end;
Doc.Close;
finally
Doc.Free;
end;
end;
V tejto ceste auditu na vás čakajú dve úskalia. Volanie TPDFlibSignDoc.Open uzamkne súbor exkluzívnym zámkom zdieľania (exclusive share lock), takže validátor, ktorý chce tiež vypočítať hash z čistých bajtov súboru pre CMS overenie, si musí súbor načítať do pamäte ešte pred jeho otvorením pre audit. Ak toto poradie otočíte, čítanie zlyhá kvôli zámku, ktorý ste sami aktivovali. Druhé úskalie je tiché: náprotivok vo flat API GetSignProcessByteRange vracia typ Integer, zatiaľ čo základné pozície (offsets) sú typu Int64. Pri súboroch nad 2 GB flat volanie bez upozornenia hodnoty oreže, čo je dôvod, prečo tento príklad načítava pozície cez triedu auditu. Za zmienku stojí aj jedna absencia: flat vrstva neobsahuje žiaden wrapper VerifySignature. Kryptografické posúdenie pochádza z triedy TPDFlibSignatureVerifier, ktorá vracia vsValid, vsInvalid alebo vsUnknown, alebo z externého validátora, ktorému vaša politika zhody už dôveruje.
Dlhodobé overovanie: DSS, VRI a časová pečiatka dokumentu
PAdES-B-LT existuje preto, lebo infraštruktúra pre odvolanie (revocation infrastructure) certifikátov je pominuteľná. Norma ETSI EN 319 142-1 §5.4.2.2 definuje Document Security Store: slovník na úrovni dokumentu obsahujúci certifikáty, CRL zoznamy a odpovede OCSP, voliteľne indexované podľa podpisu cez VRI záznamy priradené pomocou hashu z /Contents jednotlivých podpisov. Tok činností v PDFlibPas zrkadlí návrh časových pečiatok. NewPAdESDSSProcessFromFile otvorí proces; AddPAdESDSSCertificate, AddPAdESDSSCRL a AddPAdESDSSOCSP prijímajú DER blob-y; AddPAdESDSSVRI prepája vybrané údaje s konkrétnym podpisom a EndPAdESDSSProcessToFile zapíše všetko ako prírastkovú aktualizáciu. Tok na vašej strane zostáva: získanie odvolacích údajov a vyhodnotenie, či sú dostatočne čerstvé na to, aby sa oplatilo ich vložiť, je úlohou volajúceho. Knižnica garantuje, že slovníky sú štrukturálne v súlade s normou, nemôže však zaručiť, že váš OCSP odpovedač (responder) poskytol pravdivé informácie.
Archivačný koncový bod, B-LTA, pridáva časovú pečiatku dokumentu: samostatné podpisové pole, ktorého typ je DocTimeStamp namiesto Sig, vytvárané pomocou SetSignProcessDocTimeStamp s vyhradenou dĺžkou podpisu. Nenahrádza časovú pečiatku podpisu z kroku B-T. Časová pečiatka podpisu dokazuje, kedy konkrétny podpis existoval; časová pečiatka dokumentu chráni celý súbor vrátane dôkazov DSS a je prvkom, ktorý dlhodobý archív obnovuje každých niekoľko rokov z dôvodu oslabovania algoritmov. Zrelý archivačný profil obsahuje oboje. Pre čítačky, ktoré tieto štruktúry nepodporujú, zaznamenáva funkcia TPDFlibSignDoc.EnsurePAdESExtensions vývojárske rozšírenie ESIC v katalógu dokumentov, čím oznamuje, že súbor využíva funkcie definované ETSI.
Jednu reakciu na tieto skutočnosti je dobré vopred vyvrátiť, pretože vyzerá ako chyba, no nie je ňou. Prehliadač často hlási stav „neznáma platnosť“ (validity unknown) pri súbore, ktorého štruktúra PAdES je úplne správna. Dôvera a štruktúra sú dve nezávislé roviny. Prehliadač jednoducho nedokáže spojiť podpisovateľa s koreňovou autoritou, ktorej na danom stroji dôveruje, čo je bežný stav pri interných certifikačných autoritách (private CA) a testovacích certifikátoch, aj keď audit ByteRange a overenie CMS prebehli úspešne. Riešením je správna distribúcia koreňového certifikátu alebo overovanie voči zoznamom dôveryhodných adries EÚ (EU trusted lists), ak je cieľom kvalifikovaný status eIDAS, a nie zasahovanie do kódu podpisovania.
Pre pohľad zo strany auditu, čiže na výpočet podpisových polí v celej sade súborov, výpis rozložení ByteRange a hromadné načítanie úrovní DocMDP, si pozrite sprievodný článok o pracovnom prostredí pre zhodu a podpisy. Podpísané dokumenty, ktoré musia spĺňať archivačné politiky, patria do pracovného toku popísaného v časti Preflight PDF/A a PDF/UA v Delphi. Kompletnú dokumentáciu k rozhraniu API a skúšobné verzie na stiahnutie nájdete na produktovej stránke losLab PDF Library pre Delphi.