PDFiumPas rozděluje podepisování PAdES na dvě volání, takže soukromý klíč nikdy nemusí být ve vašem procesu. PreparePadesRemoteSignature zapíše přírůstkovou aktualizaci s prázdným rezervovaným místem pevné šířky /Contents a vrátí záznam požadavku nesoucí digest dokumentu SHA-256, přesný ByteRange a otisk připraveného souboru. CompletePadesRemoteSignature vezme odpojený CMS vrácený vaší podepisovací službou a vloží ho do této rezervované pozice
Mezi těmito dvěma voláními mohou uplynout minuty nebo hodiny, proces se může restartovat a práce se může přesunout na jiný stroj. Právě tato mezera je celý důvod, proč je API navrženo tímto způsobem
Proč vzdálený klíč nemůže použít obyčejné volání podepsání?
Protože SignPadesBytes předpokládá, že podepisovací operace proběhne uvnitř volání. Sestaví přírůstkovou aktualizaci, spočítá digest přes ByteRange, podepíše ho a zapíše výsledek, to vše ještě předtím, než se vrátí. To je přesně správné, když klíč žije v úložišti certifikátů Windows nebo v souboru PKCS#12, který jste načetli
Je to nemožné, když klíč žije v síťovém HSM, kvalifikovaném prostředku pro vytváření podpisu provozovaném poskytovatelem důvěryhodné služby, nebo v cloudovém podepisovacím API, které vyžaduje potvrzení uživatele na telefonu. V těchto případech není sekvence voláním funkce, ale konverzací: pošlete digest, něco jiného autentizuje člověka a CMS se vrátí až později. Synchronní API nedokáže vyjádřit „později", aniž by zablokovalo vlákno na operaci, která možná potřebuje druhý faktor
Dvoufázový protokol
První fáze připraví dokument. PDFiumPas připojí pole podpisu a slovník hodnoty, rezervuje ContentsSize bajtů hexadecimálně kódovaného místa v /Contents, spočítá ByteRange kolem této rezervace a vyprodukuje TPadesRemoteSigningRequest obsahující FormatVersion, PreparedFingerprint, DocumentDigest, čtyřprvkový ByteRange, ContentsHexOffset a ContentsSize
Jediná hodnota, kterou vaše podepisovací služba potřebuje, je DocumentDigest: SHA-256, který musí vrácené CAdES SignedData nést jako svůj message digest. Všechno ostatní v záznamu existuje proto, aby druhá fáze mohla dokázat, že soubor, který dokončuje, je soubor, ze kterého byl tento digest spočítán
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; // hexadecimálně kódované bajty rezervované pro 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 relaci, aby ji mohl dokončit pozdější běh nebo jiný stroj
Session := TFileStream.Create('contract.signreq', fmCreate);
try
SavePadesRemoteSigningRequest(Session, Request);
finally
Session.Free;
end;
SendDigestToSigningService(Request.DocumentDigest);
end;
Co Complete odmítá a proč každá kontrola existuje?
Dokončení je místo, kde návrh vzdáleného podepisování obvykle selhává, takže je validace záměrně nesmiřitelná. CompletePadesRemoteSignature odmítá připravené PDF, jehož otisk už neodpovídá požadavku, ByteRange, který neodpovídá zaznamenaným souřadnicím rezervovaného místa, pozměněné oddělovače /Contents, rezervované místo, které už není prázdné, CMS větší než rezervace, CMS, který není přesně jednou hodnotou DER, nepodporovaný tvar SignedData, chybějící atribut signing-certificate-v2 a CMS, jehož message digest se neshoduje s digestem připraveného dokumentu
Každá z těchto kontrol odpovídá reálnému selhání. Kontroly otisku a ByteRange zachytí případ, kdy někdo mezi fázemi znovu vygeneroval připravený soubor, což by vyprodukovalo podpis platný proti bajtům, které nikdo nemá. Kontrola prázdného rezervovaného místa zachytí dvojité dokončení, kdy se druhý CMS zapíše přes podpis, který už existuje. Kontrola message digestu zachytí ten nejnebezpečnější případ ze všech: správně sestavený CMS podepsaný nad jiným dokumentem, což je to, co dostanete, když si fronta splete dvě souběžné podepisovací relace. Bez ní byste vyprodukovali soubor, který vypadá podepsaně a validace ho všude odmítá, nebo hůř, který nese souhlas někoho jiného
Požadavek na signing-certificate-v2 je otázka shody s PAdES, ne integrity. ETSI EN 319 142 vyžaduje, aby byl podepisující certifikát svázán do podepsaných atributů, a CMS bez tohoto atributu není podpis PAdES, i kdyby kryptograficky ověřoval. Jeho odmítnutí při dokončení znamená, že se to dozvíte tady, ne z reportu validátoru od zákazníka, což dál rozebírá článek o tom, proč validátory odmítají 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ácené HSM nebo 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é odmítnutí nese konkrétní důvod; zalogujte ho doslovně
FailSession(E.Message);
end;
finally
Dest.Free;
Prepared.Free;
end;
end;
Překonávání hranic procesů i strojů
SavePadesRemoteSigningRequest a LoadPadesRemoteSigningRequest serializují relaci přes stabilní verzovaný binární formát, a právě to dělá tento návrh praktickým, ne jen správným. Webová aplikace může připravit dokument v jednom požadavku, uložit připravené PDF a blob relace, vrátit digest prohlížeči pro podpis čipovou kartou a soubor dokončit v úplně jiném obslužném programu požadavku
Pole FormatVersion je to, co udržuje tuto bezpečnost napříč aktualizacemi. Relace zapsaná starší verzí a načtená novější je rozpoznána nebo výslovně odmítnuta, místo aby byla mylně přečtená jako jinak tvarovaný záznam. Pokud vaše fronta dokáže držet relace celé dny, berte verzi formátu jako provozní fakt, který stojí za logování, ne jako implementační detail
Určení velikosti rezervovaného místa
ContentsSize je ten jediný parametr, nad kterým musíte přemýšlet, protože se pevně stanoví ještě předtím, než CMS existuje. Počítá hexadecimálně kódovanou rezervaci, takže 6KB DER CMS potřebuje alespoň 12 KB místa, a implementace omezuje rezervaci na 64 MiB
Rezervujete-li příliš málo, dokončení selže s chybou oversized-CMS poté, co vaše podepisovací služba už odvedla svou práci, což u zpoplatněné kvalifikované podepisovací služby znamená zbytečnou operaci navíc. Rezervujete-li příliš mnoho, každý podepsaný dokument navždy nese výplň navíc. Rozumný přístup je změřit: podepište jeden dokument svým skutečným řetězcem certifikátů, podívejte se na délku DER, zdvojnásobte ji kvůli hex kódování a pak přidejte velkorysou rezervu pro časové razítko, pokud plánujete přechod na podpis úrovně T. Řetězce s několika mezilehlými certifikáty a dlouhou odpovědí OCSP rostou rychleji, než lidé čekají
Co následuje po podpisu
Dokončený vzdálený podpis je PAdES B-B. Dlouhodobá validace potřebuje časové razítko a validační materiál, což je samostatná přírůstková aktualizace, která přidává DSS a jeho slovníky VRI pro jednotlivé podpisy, popsaná v článku o dlouhodobých podpisech s časovými razítky RFC 3161 a DSS. Tento krok je lokální: přidává certifikáty, odpovědi OCSP a CRL, z nichž žádný nepotřebuje soukromý klíč
Než výstup nasadíte do provozu, ověřte, co jste vyprodukovali, stejnou cestou kódu, jakou by použila spoléhající se strana, popsanou v článku o kontrole digitálních podpisů a úrovní PAdES. Podepisování a ověřování je jiný kód, a pipeline vzdáleného podepisování je přesně to místo, kde se tyto dva kódy mohou rozejít, aniž by si toho kdokoli všiml, dokud to neřekne externí validátor
PDFiumPas je komponenta pro Delphi a Lazarus postavená kolem enginu PDFium s nativním zásobníkem PAdES v Pascalu, takže podepisování, časová razítka i validace fungují bez externích nástrojů příkazové řádky. Úplná dokumentace API i zkušební verze jsou na stránce PDFium Delphi component