Odborný článok

Overovanie digitálnych podpisov PDF v Delphi pomocou HotPDF

HotPDF overuje digitálne podpisy v načítaných dokumentoch PDF prostredníctvom troch metód THotPDF: GetLoadedSignatureInfo, VerifyLoadedSignature a VerifyLoadedSignatureEx, predstavených vo verzii v2.259.0. Tento komponent znova prepočítava hashe segmentov /ByteRange pôvodného súboru, kontroluje atribút CMS messageDigest a vykonáva overenie RSA PKCS#1 v1.5 voči vloženému certifikátu podpisovateľa, pričom vracia svValid, ak sú bajty dokumentu neporušené

Scenár je všedný, no stávky sú vysoké. Druhá strana vráti podpísanú zmluvu, váš systém ju potrebuje zaevidovať a niekto položí jedinú otázku, na ktorej záleží: je to ten istý dokument, ktorý sme poslali, bajt po bajte, podpísaný deklarovaným certifikátom? Odpoveď v kóde predstavuje overovaciu stranu príbehu o podpisoch; podpisovacia strana, teda vytváranie a vkladanie podpisov PAdES, je popísaná v sprievodnom článku o vytváraní digitálnych podpisov PAdES pomocou HotPDF. Tento článok sa venuje opačnému smeru: PDF prichádza už podpísané a vy chcete programový verdikt namiesto snímky obrazovky so zeleným zaškrtnutím v Arobate

Ako podpísané PDF dokazuje, že s ním nebolo manipulované?

Podpis PDF chráni konkrétne bajtové rozsahy súboru, nie abstraktný pojem „dokument“. Norma ISO 32000-1 §12.8 definuje tento mechanizmus: formulárové pole podpisu nesie slovník, ktorého položka /Contents obsahuje kontajner CMS SignedData (RFC 5652) a ktorého pole /ByteRange pomenúva presné oblasti súboru, ktoré podpis pokrýva, podľa §12.8.1. Pole je zoznamom dvojíc posunu (offset) a dĺžky, v praxi ide o dva segmenty: všetko pred hexadecimálnym reťazcom /Contents a všetko po ňom. Hodnota podpisu nemôže pokrývať samu seba, takže súbor se hashuje okolo tejto diery

Tento dizajn má dôsledok, ktorý formuje celé API: overovanie musí hashovať pôvodné serializované bajty presne tak, ako ležia na disku. Analyzovaný model objektov je na to nepoužiteľný, pretože opätovná serializácia aj nezmeneného dokumentu vytvorí odlišné bajty. HotPDF preto overuje podpis voči zdrojovému súboru, z ktorého bol dokument načítaný, alebo voči prúdu TStream surových bajtov, ktoré dodáte, a nikdy nie voči jeho reprezentácii v pamäti

Čítanie metadát podpisu pred samotným overením

Metóda GetLoadedSignatureInfo analyzuje slovník podpisu a jeho kontajner CMS bez toho, aby sa dotkla jediného bajtu dokumentu, čo z nej robí správne prvé volanie, ak potrebujete iba zobraziť, kto a kedy dokument podpísal. Polia podpisov sú indexované od 0 v poradí formulárových polí a GetLoadedSignatureFieldCount vám povie, koľko ich existuje. Vrátený záznam THPDFSignatureInfo obsahuje názov poľa, /SubFilter, spoločný názov (common name) certifikátu podpisovateľa, rozlišovacie názvy (distinguished names) subjektu a vydavateľa, sériové číslo, dátumy platnosti, čas podpisu (z podpísaného atribútu, ak je prítomný, inak zo záznamu /M slovníka), názov algoritmu hashovania a reťazce /Reason, /Location a /ContactInfo. Jeho člen Status zostáva v stave svNotVerified, čo je férové označenie pre „analyzované, neoverené“

var
  Pdf: THotPDF;
  Info: THPDFSignatureInfo;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('signed-contract.pdf');
    for I := 0 to Pdf.GetLoadedSignatureFieldCount - 1 do
    begin
      Info := Pdf.GetLoadedSignatureInfo(I);
      Writeln('Field:     ', Info.FieldName);
      Writeln('Signer:    ', Info.SignerName);
      Writeln('Issuer:    ', Info.IssuerDN);
      Writeln('Algorithm: ', Info.HashAlgorithm);
      Writeln('SubFilter: ', Info.SubFilter);
    end;
  finally
    Pdf.Free;
  end;
end;

Spustenie kryptografickej kontroly

Metóda VerifyLoadedSignatureEx vykonáva úplné overenie pre dokument načítaný zo súboru a v jednom volaní vracia vyplnený záznam informácií: znova otvorí zdrojový súbor, hashuje segmenty /ByteRange pomocou algoritmu hashovania SignerInfo, porovná výsledok s podpísaným atribútom messageDigest (RFC 5652 §5.4) a potom overí RSA podpis nad DER SET re-kódovaním podpísaných atribútov. Ak podpis neobsahuje žiadne podpísané atribúty, kontrola RSA prebieha priamo nad hashom dokumentu. Podporované sú podpisy RSA PKCS#1 v1.5 s hashmi SHA-1, SHA-256, SHA-384 alebo SHA-512, čo pokrýva subfiltre adbe.pkcs7.detached a ETSI.CAdES.detached vytvárané bežnými podpisovacími nástrojmi

var
  Status: THPDFSignatureVerifyStatus;
  Info: THPDFSignatureInfo;
begin
  Status := Pdf.VerifyLoadedSignatureEx(0, Info);
  case Status of
    svValid:
      if Info.CoversWholeDocument then
        Writeln('Valid; signature covers the whole file')
      else
        Writeln('Valid; file was extended after signing');
    svDigestMismatch:
      Writeln('Document bytes changed after signing');
    svSignatureInvalid:
      Writeln('RSA check failed over signed attributes');
    svUnsupportedAlgorithm:
      Writeln('Non-RSA key or unknown digest algorithm');
    svMalformed:
      Writeln('CMS container could not be parsed');
    svSourceUnavailable:
      Writeln('No source bytes; use the TStream overload');
  end;
end;

Čo skutočne garantuje platný podpis?

Stav svValid znamená presne toto: bajty označené v /ByteRange sa zhodujú s hodnotou, ktorú podpisovateľ podpísal, a podpis sa overí pod verejným kľúčom certifikátu vloženého v kontajneri CMS. To predstavuje integritu bajtov plus väzbu na kľúč a nič viac. Overovanie reťazca certifikátov a dôveryhodnosti je výslovne mimo rozsahu overovača HotPDF: neprechádza reťazec ku koreňu, nekontroluje odvolanie certifikátov (revocation) ani nekonzultuje žiadne úložisko dôveryhodnosti. Samopodpísaný certifikát od útočníka, ktorý znova podpísal upravený dokument, sa overí ako svValid, pretože matematika je interne konzistentná. To, či je podpisovateľ tým, za koho sa vydáva, a či by mu mal niekto dôverovať, je strategické rozhodnutie, ktoré patrí do samostatnej vrstvy, či už ide o zoznam povolených certifikátov vašej organizácie, úložisko certifikátov systému Windows alebo overovaciu autoritu

Príznak CoversWholeDocument stráži jemnejšiu medzeru. Podpis vždy pokrýva iba svoj /ByteRange, pričom mechanizmus inkrementálnych aktualizácií PDF umožňuje pripájať obsah za podpis bez toho, aby ho zneplatnil, čo je zámerom a takto fungujú toky práce s viacerými podpismi. Príznak sa počíta počas overovania a je pravdivý iba vtedy, keď oba segmenty plus medzera pre /Contents pokrývajú celý súbor. Keď sa vráti stav svValid s hodnotou CoversWholeDocument false, podpísaná revízia je nedotknutá, ale súbor obsahuje neskoršie prírastky a o tom, či zmeny v týchto prírastkoch tolerovať, by mal rozhodnúť váš pracovný postup

Dokumenty načítané z prúdu a šifrované dokumenty potrebujú vlastné zdrojové bajty

Bezparametrické metódy VerifyLoadedSignature a VerifyLoadedSignatureEx závisia od toho, či si komponent pamätá, z ktorého súboru dokument pochádza. Ak načítate dokument z prúdu (streamu), neexistuje žiadny názov súboru na opätovné otvorenie; to isté platí po ceste opätovného načítania hesla používanej pre šifrované dokumenty, čo je postup opísaný v článku o šifrovaní PDF pomocou AES-256 v HotPDF. V oboch prípadoch preťaženia závislé od súborov vracajú svSourceUnavailable namiesto hádania. Riešením je preťaženie TStream, ktoré vám umožňuje odovzdať pôvodné surové bajty odtiaľ, kde ste ich uchovali — zo súboru, ktorý stále máte, z pamäťovej vyrovnávacej pamäte alebo z databázového objektu blob

var
  Src: TFileStream;
  Status: THPDFSignatureVerifyStatus;
  Info: THPDFSignatureInfo;
begin
  // Stream-loaded document: the component holds no source
  // file name, so supply the original bytes yourself.
  Src := TFileStream.Create('signed-contract.pdf',
    fmOpenRead or fmShareDenyWrite);
  try
    Status := Pdf.VerifyLoadedSignature(0, Src, Info);
    if Status <> svValid then
      Writeln('Verification failed: ', Ord(Status));
  finally
    Src.Free;
  end;
end;

Oznamovanie toho, čo nemôžete overiť

Overovač, ktorý pozná iba stavy „platný“ a „neplatný“, nesprávne vyhodnotí dokumenty, ktorým jednoducho nerozumie, preto zoznam stavov oddeľuje prípady, ktoré by vaše používateľské rozhranie malo rozlišovať. svDigestMismatch znamená, že bajty dokumentu sa po podpísaní zmenili, čo je klasický príznak neoprávnenej manipulácie. svSignatureInvalid znamená, že hash bajtov je správny, ale zlyhala kontrola RSA, čo poukazuje na poškodenú alebo sfalšovanú hodnotu podpisu. svUnsupportedAlgorithm je korektná odpoveď pre kľúče ECDSA a neznáme hashe: podpis môže byť úplne v poriadku, HotPDF ho len nedokáže skontrolovať a jeho nahlásenie ako „neplatný“ by poškodilo zdravý dokument. svMalformed označuje kontajner CMS, ktorý nebolo možné vôbec analyzovať. Pre kontroly typu brány (gate-style) vracia VerifyAllLoadedSignatures hodnotu true iba vtedy, keď existuje aspoň jedno podpisové pole a každé z nich sa overí ako svValid, čo je pohodlná hodnota boolean pre linku príjmu archívov, ktorá odmieta všetko ostatné

Overovanie podpisov, podpisovanie PAdES, šifrovanie AES-256 a API na úpravu načítaných dokumentov sa dodávajú v rovnakej natívnej knižnici VCL pre Delphi a C++Builder, bez externých závislostí na DLL; kompletný zoznam funkcií a podporovaných verzií IDE nájdete na stránke produktu HotPDF Component