Obdrželi jste podepsané PDF a potřebujete ve svém prohlížeči zobrazit, kdo jej podepsal, kdy bylo podepsáno, zda podpis pokrývá celý soubor a jak daleko sahá z hlediska dlouhodobé shody (compliance). Komponenta PDFium Component pro Delphi a Lazarus odpovídá na všechny čtyři otázky pomocí volání pouze pro čtení: FPDF_GetSignatureCount a rodina FPDFSignatureObj_* vystavují slovník podpisu a TPdf.ValidatePades klasifikuje základní úroveň PAdES. Toto je první ze tří článků o podpisech PDF s PDFium; dva následující pokrývají vytvoření podpisu B-B a přidání dlouhodobých časových razítek. Jedna hranice však patří hned na začátek: všechno zde je inspekce a čtení toho, co podpis tvrdí, je jiná práce než ověřování jeho kryptografie nebo rozhodování o tom, zda důvěřujete podepisující osobě
Proč slovník podpisu PDF není jen shluk bajtů
Podpis PDF je slovník, nikoli neprůhledná příloha, a jeho dva nejdůležitější záznamy vám řeknou, kolik ze souboru je skutečně chráněno. Specifikace ISO 32000-1 §12.8 definuje slovník podpisu s položkami /ByteRange a /Contents. /Contents obsahuje hexadecimálně kódovanou strukturu CMS SignedData (RFC 5652), což je kryptografická obálka, která nese certifikát podepisujícího, podepsané atributy a samotnou hodnotu podpisu. /ByteRange je část, kterou vývojáři podceňují: je to pole dvou rozsahů offset-délka, které dohromady pokrývají celý soubor kromě hexadecimálního řetězce /Contents. Tato mezera je přesně místem, kde leží bajty podpisu, a dva rozsahy na obou stranách jsou přesně tím, k čemu se podpis zavazuje
Návrh ByteRange je to, co činí inkrementální ukládání auditovatelným. Protože podepisující nemůže hashovat bajty podpisu, které ještě neexistují, soubor se rozdělí kolem zástupného symbolu /Contents a vše ostatní se zahashuje do podpisu. Podpis, hisž ByteRange nedosahuje na konec souboru, je varovným signálem: obsah připojený po pokrytém rozsahu prostřednictvím pozdější inkrementální aktualizace by podpis neporušil, i když mění to, co čtenář vidí. Takže první věc, kterou seriózní inspektor kontroluje, není to, kdo podepsal, ale zda podpis pokrývá bajty, které se zdá, že schvaluje
Čtení slovníku podpisu pomocí rozhraní API PDFium pouze pro čtení
Komponenta PDFium zpřístupňuje slovník podpisu prostřednictvím dvou členů pouze pro čtení: SignatureCount a záznamu Signature[Index]. Pod kapotou volají FPDF_GetSignatureCount, FPDF_GetSignatureObject a přístupové rutiny FPDFSignatureObj_* pro /SubFilter, /ByteRange, /Contents, /Reason a čas podpisu. Fráze „pouze pro čtení“ je zde klíčová: PDFium dokáže podpisy vyjmenovat a číst, ale nemá žádné API pro jejich vytváření nebo zápis, což je důvod, proč je podepisující strana této série implementována samotnou knihovnou, a nikoli PDFium
var
Pdf: TPdf;
i: Integer;
Sig: TPdfSignature;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'contract-signed.pdf';
Pdf.Active := True;
for i := 0 to Pdf.SignatureCount - 1 do
begin
Sig := Pdf.Signature[i];
Writeln('SubFilter : ', Sig.Encoding); // ETSI.CAdES.detached, adbe.pkcs7.detached, ...
Writeln('Signed at : ', Sig.Time); // řetězec data podepisujícího, např. D:20260708120000+02'00'
Writeln('Reason : ', Sig.Reason);
Writeln('CMS length: ', Length(Sig.Content)); // nezpracovaná data DER SignedData převzatá z /Contents
Writeln('DocMDP : ', Sig.Permission); // 0 = bez certifikace, 1..3 = úroveň MDP
end;
finally
Pdf.Free;
end;
end;
Každý záznam TPdfSignature se mapuje přímo na slovník. Encoding je /SubFilter, což je jediné nejvíce diagnostické pole, protože pojmenovává obsluhu podpisu a okamžitě odděluje moderní podpis ETSI.CAdES.detached od zastaralého nebo zakázaného. Time je autorem deklarovaný čas podpisu jako řetězec data PDF, což je tvrzení podepisujícího a nikoli důvěryhodný čas. Content jsou nezpracovaná data CMS SignedData a Permission vystavuje úroveň certifikace DocMDP (0 pro běžný podpis schválení, 1 až 3 pro podpis certifikace, který uzamyká pozdější změny). Jediné pole, které záznam nezpřístupňuje, je analyzovaný ByteRange, a toto vynechání je záměrné, protože ValidatePades provádí aritmetiku pokrytí ByteRange za vás, místo aby vás nutilo dělat to ručně
Jaký je rozdíl mezi PAdES B-B, B-T, B-LT a B-LTA?
Čtyři základní úrovně PAdES tvoří žebříček od minimálně platného podpisu až po podpis vytvořený tak, aby přežil desetiletí archivace, přičemž každá úroveň striktně obsahuje úroveň pod ní. Specifikace ETSI EN 319 142-1 je definuje jako B-B, B-T, B-LT a B-LTA. B-B (Basic) je podpis plus povinné podepsané atributy a nic víc. B-T (Timestamp) přidává důvěryhodné časové razítko RFC 3161 nad podpis, takže okamžik podepsání je potvrzen autoritou časového razítka, místo aby byl deklarován hodinami podepisujícího. B-LT (Long-Term) integruje validační materiály — řetězec certifikátů a volitelně odpovědi OCSP nebo CRL — do souboru, takže podpis lze stále validovat i po letech, kdy je vydávající infrastruktura pryč. B-LTA (Long-Term s archivním časovým razítkem) obaluje tyto materiály časovým razítkem dokumentu, čímž chrání samotná dlouhodobá data a dává vám bod pro opětovné orazítkování dříve, než podkladová kryptografie zastará
Praktický výklad je o časovém horizontu. Podpis B-B odpovídá na otázku „podepsal to někdo“. B-T odpovídá „a kdy, prokazatelně“. B-LT odpovídá „a mohu to stále zkontrolovat po vypršení platnosti certifikátů“. B-LTA odpovídá „a bude tato kontrola platit i za dvacet let“. Regulační profily si vybírají úroveň: mnoho kontextů elektronické fakturace a eIDAS vyžaduje alespoň B-T a archivační mandáty sahají po B-LT nebo B-LTA. Vědět, na kterou úroveň dokument skutečně dosáhne předtím, než jej přijmete nebo odmítnete, je celým smyslem kroku inspekce
Detekce základní úrovně pomocí TPdf.ValidatePades
Komponenta PDFium redukuje celou otázku úrovně na jediné volání. TPdf.ValidatePades vrací záznam TPadesValidationResult, jehož pole Level je výčtového typu TPadesLevel — plNone, plUnknown, plB_B, plB_T, plB_LT nebo plB_LTA — spolu se sadou problémů (issues), počtem podpisů a počtem časových razítek dokumentu. Úroveň se odvozuje monotónně: validátor nejprve stanoví B-B, poté povýší na B-T, pokud je přítomno časové razítko podpisu nebo dokumentu, na B-LT, pokud katalog obsahuje /DSS s certifikáty a identifikátor úrovně 1 /Extensions /ESIC, a na B-LTA, pokud je přítomno časové razítko dokumentu i identifikátor úrovně 2 ESIC. Dva pomocníci činí výsledek použitelným: IsCompliant je True pouze tehdy, když úroveň dosáhne alespoň B-B a sada problémů je prázdná, a IsCompliantAt vám umožňuje prosadit minimální úroveň zásad (policy floor), jako je plB_T
var
Pdf: TPdf;
R: TPadesValidationResult;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'contract-signed.pdf';
Pdf.Active := True;
R := Pdf.ValidatePades;
case R.Level of
plNone: Writeln('No PAdES signature present');
plUnknown: Writeln('Signature present but level undeterminable');
plB_B: Writeln('PAdES B-B (základní)');
plB_T: Writeln('PAdES B-T (důvěryhodné časové razítko)');
plB_LT: Writeln('PAdES B-LT (integrovaný dlouhodobý materiál)');
plB_LTA: Writeln('PAdES B-LTA (archivní časové razítko)');
end;
Writeln('Signatures : ', R.SignatureCount);
Writeln('DocTimeStamps: ', R.DocTimeStampCount);
if R.IsCompliantAt(plB_T) then
Writeln('Splňuje minimální úroveň B-T')
else
Writeln('Pod požadovanou úrovní B-T');
finally
Pdf.Free;
end;
end;
Proč je adbe.pkcs7.sha1 zakázaným SubFilterem?
Protože SHA-1 je prolomený a obsluha adbe.pkcs7.sha1 jej obsahuje v sobě. Tento SubFilter předběžně zahashuje dokument pomocí SHA-1 před jeho zabalením do PKCS#7 a SHA-1 je již léta zranitelný vůči kolizím, takže norma EN 319 142-1 odstavec 6.3 jej pro základní podpis přímo zakazuje. ValidatePades vyvolá ppeiForbiddenSubFilter, když uvidí adbe.pkcs7.sha1 nebo adbe.x509.rsa_sha1, a vyvolá ppeiBadDigestAlgorithm, když samotný CMS používá MD5 nebo SHA-1 jako otisk zprávy (odstavec 6.2.1). Jedná se o dvě odlišné kontroly, které zachycují stejnou třídu slabin ve dvou různých vrstvách
Sada problémů (issues) má celkem 26 členů a ty, se kterými se setkáte nejčastěji, se točí kolem struktury a pokrytí. ppeiByteRangeNotCoveringFile je dříve popsaná kontrola pokrytí. ppeiForbiddenCertKey se spustí, když slovník podpisu obsahuje položku /Cert, kterou PAdES zakazuje, protože řetězec musí místo toho žít uvnitř CMS SignedData.certificates. ppeiMissingSigningCertificate, ppeiMissingContentType a ppeiMissingMessageDigest označují chybějící povinné podepsané atributy a ppeiDetachedContentViolation zachycuje podpis, který nesprávně integruje podepsaný obsah, místo aby jej oddělil. Vyjmenování této sady promění pouhé odmítnutí v diagnózu, kterou můžete zaznamenat do logu
var
R: TPadesValidationResult;
Issue: TPadesValidationIssue;
begin
R := Pdf.ValidatePades;
if R.Issues <> [] then
for Issue := Low(TPadesValidationIssue) to High(TPadesValidationIssue) do
if Issue in R.Issues then
Writeln('Issue: ',
GetEnumName(TypeInfo(TPadesValidationIssue), Ord(Issue)));
end;
Co ValidatePades nekontroluje
ValidatePades validuje strukturu, nikoli důvěryhodnost, a záměna jednoho za druhé je nebezpečnou chybou. Výsledek plB_LTA znamená, že dokument obsahuje správně formátovaný podpis B-LTA se všemi povinnými atributy, materiály a časovými razítky na správných místech — neznamená to však, že podpis je kryptograficky platný, že se certifikát řetězí k rootu, kterému důvěřujete, nebo že žádný certifikát v řetězci nebyl odvolán. Validátor záměrně neprovádí žádné kryptografické ověřování: nepřepočítává podpis nad ByteRange, nesestavuje ani nevyhodnocuje řetězec důvěry a nekontroluje stav odvolání OCSP nebo CRL. Toto rozdělení je záměrné a užitečné, protože strukturální inspekce je rychlá, plně deterministická a nevyžaduje žádné klíče, síť ani kryptografii platformy, takže ValidatePades běží identicky na Windows, Linuxu a macOS jako čistý Pascal nad bajty souboru. Validace řetězce důvěry je naproti tomu neoddělitelná od zásad (policy) — kterým rootům důvěřujete, jak získáváte odvolání, jak přísná je vaše tolerance časových razítek — a patří do pozdější fáze, která závisí na úložišti certifikátů platformy, takže berte úspěšný průchod ValidatePades jako nezbytnou bránu potvrzující, že podpis má správný tvar, a poté structurally bezvadný podpis předejte ke skutečnému kryptografickému ověření, než se na něj spolehnete
Tento strukturální průchod je tím správným místem, kde začít, a přirozeně se doplňuje s širšími kontrolami pouze pro čtení v auditu bezpečnostních rizik PDF s komponentou PDFium a s prací na shodě formátů, jako je validace dokumentů PDF/X připravených k tisku. Jakmile dokážete podpis přečíst a klasifikovat, dalším krokem je jeho vytvoření: druhý článek v této sérii se zabývá podepisováním PDF pomocí PAdES B-B a třetí to rozšiřuje na důvěryhodná časová razítka a dlouhodobé podpisy B-LT a B-LTA. Inspekce podpisů pouze pro čtení a klasifikátor ValidatePades zde ukázané jsou součástí komponenty PDFium Component pro Delphi, C++Builder a Lazarus