PDFiumPas rozdeľuje podpisovanie PAdES na dve volania tak, aby súkromný kľúč nikdy nemusel byť vo vašom procese. PreparePadesRemoteSignature zapíše inkrementálnu aktualizáciu s prázdnym zástupným symbolom /Contents pevnej šírky a vráti záznam požiadavky nesúci digest dokumentu SHA-256, presný ByteRange a odtlačok pripraveného súboru. CompletePadesRemoteSignature zoberie oddelený (detached) CMS, ktorý vráti vaša podpisová služba, a vloží ho do tohto rezervovaného miesta
Medzi týmito dvoma volaniami môžu prejsť minúty alebo hodiny, proces sa môže reštartovať, a práca sa môže presunúť na iný stroj. Práve táto medzera je celý dôvod, prečo je API navrhnuté týmto spôsobom
Prečo vzdialený kľúč nemôže použiť obyčajné volanie podpisovania?
Pretože SignPadesBytes predpokladá, že sa podpisová operácia odohráva vnútri volania. Zostaví inkrementálnu aktualizáciu, vypočíta digest nad ByteRange, podpíše ho a zapíše výsledok, to všetko ešte predtým, než sa vráti. To je presne správne vtedy, keď kľúč žije v certifikátovom úložisku Windows alebo v súbore PKCS#12, ktorý ste načítali
Je to nemožné vtedy, keď kľúč žije v sieťovom HSM, kvalifikovanom zariadení na vytváranie podpisov prevádzkovanom poskytovateľom dôveryhodných služieb, alebo v cloudovom podpisovom API, ktoré vyžaduje, aby používateľ potvrdil na telefóne. V týchto prípadoch nejde o volanie funkcie, ale o konverzáciu: pošlete digest, niečo iné autentizuje človeka, a CMS sa vráti neskôr. Synchrónne API nedokáže vyjadriť „neskôr“ bez blokovania vlákna na operácii, ktorá môže potrebovať druhý faktor
Dvojfázový protokol
Prvá fáza pripraví dokument. PDFiumPas pripojí pole podpisu a slovník hodnôt, rezervuje ContentsSize bajtov priestoru kódovaného ako hex v /Contents, vypočíta ByteRange okolo tejto rezervácie a vyprodukuje TPadesRemoteSigningRequest obsahujúci FormatVersion, PreparedFingerprint, DocumentDigest, štvorprvkový ByteRange, ContentsHexOffset a ContentsSize
Jediná hodnota, ktorú vaša podpisová služba potrebuje, je DocumentDigest: SHA-256, ktorý musí vrátený CAdES SignedData niesť ako svoj message digest. Všetko ostatné v tomto zázname existuje na to, aby druhá fáza mohla dokázať, že súbor, ktorý dokončuje, je ten istý súbor, z ktorého bol tento digest vypočítaný
uses
FPdfPades;
var
Options: TPadesRemoteSignOptions;
Request: TPadesRemoteSigningRequest;
Source, Prepared, Session: TFileStream;
begin
Options := TPadesRemoteSignOptions.Default;
Options.Reason := 'Approved by finance';
Options.Location := 'Lisbon';
Options.Name := 'A. Moreira';
Options.SigningTimeUtc := NowUtc;
Options.ContentsSize := 16384; // hex bajtov rezervovaných pre CMS
Source := TFileStream.Create('contract.pdf', fmOpenRead or fmShareDenyWrite);
Prepared := TFileStream.Create('contract.prepared.pdf', fmCreate);
try
PreparePadesRemoteSignature(Source, Prepared, Options, Request);
finally
Prepared.Free;
Source.Free;
end;
// Uložte session, aby ju mohol dokončiť neskorší beh — alebo iný stroj
Session := TFileStream.Create('contract.signreq', fmCreate);
try
SavePadesRemoteSigningRequest(Session, Request);
finally
Session.Free;
end;
SendDigestToSigningService(Request.DocumentDigest);
end;
Čo Complete odmietne, a prečo existuje každá kontrola?
Dokončenie je miesto, kde návrh vzdialeného podpisovania zvyčajne zlyháva, takže validácia je zámerne nemilosrdná. CompletePadesRemoteSignature odmietne pripravený PDF, ktorého odtlačok už nesúhlasí s požiadavkou, ByteRange, ktorý nesúhlasí so zaznamenanými súradnicami zástupného symbolu, upravené oddeľovače /Contents, zástupný symbol, ktorý už nie je prázdny, CMS väčší než rezervácia, CMS, ktorý nie je presne jednou hodnotou DER, nepodporovaný tvar SignedData, chýbajúci atribút signing-certificate-v2, a CMS, ktorého message digest sa nerovná pripravenému digestu dokumentu
Každá z nich zodpovedá reálnemu zlyhaniu. Kontroly odtlačku a ByteRange odchytia prípad, keď niekto medzi fázami znovu vygeneroval pripravený súbor, čo by vyprodukovalo podpis, ktorý sa overí voči bajtom, ktoré nikto nemá. Kontrola prázdneho zástupného symbolu odchytí dvojité dokončenie, kde sa druhý CMS zapíše navrch podpisu, ktorý už existuje. Kontrola message digestu odchytí ten najnebezpečnejší prípad zo všetkých: správne vytvorený CMS podpísaný nad iným dokumentom, čo dostanete, keď fronta pomieša dve súbežné podpisové session. Bez nej by ste vyprodukovali súbor, ktorý vyzerá podpísaný a všade zlyhá pri validácii, alebo horšie, ktorý nesie schválenie niekoho iného
Požiadavka na signing-certificate-v2 je otázka zhody s PAdES, nie otázka integrity. ETSI EN 319 142 vyžaduje, aby bol podpisový certifikát naviazaný do podpísaných atribútov, a CMS, ktorý tento atribút nemá, nie je podpisom PAdES, ani keď sa kryptograficky overí. Odmietnutie pri dokončení znamená, že sa o tom dozviete tu, a nie v reporte validátora od zákazníka — téma podrobnejšie rozobraná v článku prečo validátory odmietajú podpisy PAdES
var
Request: TPadesRemoteSigningRequest;
Session, Prepared, Dest: TFileStream;
CmsDer: TBytes;
begin
Session := TFileStream.Create('contract.signreq', fmOpenRead);
try
Request := LoadPadesRemoteSigningRequest(Session);
finally
Session.Free;
end;
CmsDer := FetchDetachedCmsFromService; // vrátené HSM alebo TSP
Prepared := TFileStream.Create('contract.prepared.pdf', fmOpenRead);
Dest := TFileStream.Create('contract.signed.pdf', fmCreate);
try
try
CompletePadesRemoteSignature(Prepared, Dest, Request, CmsDer);
except
on E: EPadesCrypto do
// Každé odmietnutie nesie konkrétny dôvod; logujte ho doslovne
FailSession(E.Message);
end;
finally
Dest.Free;
Prepared.Free;
end;
end;
Prekračovanie hraníc procesov a strojov
SavePadesRemoteSigningRequest a LoadPadesRemoteSigningRequest serializujú session cez stabilný verziovaný binárny formát, a práve to robí návrh praktickým, nielen správnym. Webová aplikácia môže pripraviť dokument v jednej požiadavke, uložiť pripravený PDF a blob session, vrátiť digest prehliadaču na podpis smart kartou, a dokončiť súbor v úplne inom obslužnom bloku požiadavky
Pole FormatVersion je to, čo udržiava toto bezpečné naprieč aktualizáciami. Session zapísaná staršou verziou a načítaná novšou sa buď explicitne rozpozná, alebo odmietne, namiesto toho, aby sa nesprávne prečítala ako inak tvarovaný záznam. Ak vaša fronta môže držať session celé dni, berte verziu formátu ako prevádzkový fakt, ktorý sa oplatí logovať, nie ako implementačný detail
Určenie veľkosti zástupného symbolu
ContentsSize je jediný parameter, o ktorom musíte premýšľať, pretože je pevne stanovený skôr, než CMS existuje. Počíta rezerváciu kódovanú ako hex, takže 6 KB DER CMS potrebuje aspoň 12 KB priestoru, a implementácia obmedzuje rezerváciu na 64 MiB
Rezervujte málo, a dokončenie zlyhá s chybou nadmerne veľkého CMS potom, čo vaša podpisová služba už vykonala svoju prácu, čo pri spoplatnenej kvalifikovanej podpisovej službe znamená premrhanú operáciu. Rezervujte príliš veľa, a každý podpísaný dokument navždy nesie výplň. Rozumný prístup je zmerať: podpíšte jeden dokument svojou reálnou reťazou certifikátov, pozrite sa na dĺžku DER, zdvojnásobte ju kvôli hex kódovaniu, a potom pridajte veľkorysú rezervu pre časovú pečiatku, ak plánujete prejsť na podpis úrovne T. Reťaze s viacerými medziľahlými certifikátmi a dlhou odpoveďou OCSP rastú rýchlejšie, než ľudia čakajú
Čo prichádza po podpise
Dokončený vzdialený podpis je PAdES B-B. Dlhodobá validácia potrebuje časovú pečiatku a validačný materiál, čo je samostatná inkrementálna aktualizácia, ktorá pridáva DSS a jej slovníky VRI pre jednotlivé podpisy, opísaná v článku dlhodobé podpisy s časovými pečiatkami RFC 3161 a DSS. Tento krok je lokálny: pridáva certifikáty, odpovede OCSP a CRL, z ktorých žiadny nepotrebuje súkromný kľúč
Pred nasadením overte to, čo ste vyprodukovali, tou istou cestou kódu, akú by použila spoliehajúca sa strana, opísanou v článku inšpekcia digitálnych podpisov a úrovní PAdES. Podpisovanie a overovanie sú odlišný kód, a pipeline vzdialeného podpisovania je presne to miesto, kde sa tieto dva môžu rozísť bez toho, aby si to niekto všimol, kým to nepovie externý validátor
PDFiumPas je komponent pre Delphi a Lazarus postavený nad enginom PDFium s natívnym zásobníkom PAdES v Pascale, takže podpisovanie, časová pečiatka a validácia fungujú bez externých nástrojov príkazového riadku. Úplná dokumentácia API a skúšobná verzia sú na stránke PDFium Delphi component