Műszaki cikk

PDF digitális aláírások ellenőrzése Delphi-ben a HotPDF segítségével

A HotPDF három, a v2.259.0 verzióban bevezetett THotPDF metóduson keresztül ellenőrzi a betöltött PDF dokumentumok digitális aláírásait: GetLoadedSignatureInfo, VerifyLoadedSignature és VerifyLoadedSignatureEx; a komponens újrahasheli az eredeti fájl /ByteRange szegmenseit, ellenőrzi a CMS messageDigest attribútumát, és lefuttat egy RSA PKCS#1 v1.5 ellenőrzést a beágyazott aláírói tanúsítvánnyal szemben, svValid értéket adva vissza, ha a dokumentum bájtok épek

A forgatókönyv hétköznapi, a tét viszont nem az; a szerződő fél visszaküld egy aláírt szerződést, a munkafolyamatnak be kell archiválnia azt, és valaki felteszi az egyetlen lényeges kérdést: bájtról bájtra ez-e az a dokumentum, amelyet elküldtünk, azzal a tanúsítvánnyal aláírva, amelyet állít? Ezt kódban megválaszolni az aláírás-történet ellenőrzési oldala; az aláíró oldal, vagyis a PAdES aláírások létrehozása és beágyazása a PAdES digitális aláírások HotPDF-fel történő létrehozásáról szóló társcikkben található; ez a cikk a másik irányról szól: egy PDF már aláírva érkezik, Ön pedig programozott döntést szeretne az Acrobat zöld pipájának képernyőképe helyett

Hogyan bizonyítja az aláírt PDF, hogy nem módosították?

A PDF-aláírás a fájl meghatározott bájttartományait védi, nem pedig „a dokumentum” elvont fogalmát; az ISO 32000-1 §12.8 határozza meg a mechanizmust: az aláírás űrlapmező egy olyan szótárat hordoz, amelynek /Contents bejegyzése egy CMS SignedData tárolót (RFC 5652) tartalmaz, és amelynek /ByteRange tömbje megnevezi a pontos fájltartományokat, amelyeket az aláírás lefed, a §12.8.1 szerint; a tömb az eltolás és hossz párok listája, a gyakorlatban két szegmens: minden, ami a /Contents hexadecimális karakterlánc előtt van, és minden, ami utána; az aláírás értéke nem fedheti le önmagát, így a fájl ezen rés körül van hashelve

Ennek a felépítésnek van egy olyan következménye, amely meghatározza az egész API-t: az ellenőrzésnek az eredeti sorosított bájtokat kell hashelnie, pontosan úgy, ahogy a lemezen vannak; az értelmezett objektummodell használhatatlan ehhez, mert még a változatlan dokumentum újra-sorosítása is más bájtokat eredményez; a HotPDF ezért a forrásfájllal szemben ellenőriz, amelyből a dokumentumot betöltötték, vagy az Ön által biztosított nyers bájtokat tartalmazó TStream-mel szemben, soha nem a memóriabeli reprezentációval szemben

Aláírás metaadatainak kiolvasása bármilyen ellenőrzés előtt

A GetLoadedSignatureInfo egyetlen dokumentum-bájt megérintése nélkül elemzi az aláírás-szótárat és annak CMS tárolóját, így ez a megfelelő első hívás, ha csak azt kell megjelenítenie, hogy ki és mikor írta alá; az aláírásmezők 0-tól vannak indexelve az űrlapmezők sorrendjében, és a GetLoadedSignatureFieldCount megadja, hány létezik; a visszaadott THPDFSignatureInfo rekord hordozza a mezőnevet, a /SubFilter-t, az aláíró tanúsítványának általános nevét, a tulajdonos és a kibocsátó megkülönböztetett neveit, a sorozatszámot, az érvényességi dátumokat, az aláírás idejét (a signed attribútumból, ha jelen van, egyébként a szótár /M bejegyzéséből), a kivonat-algoritmus nevét, valamint a /Reason, /Location és /ContactInfo karakterláncokat; a Status tagja svNotVerified marad, ami az „értelmezett, nem ellenőrzött” korrekt jelölése

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('Mező:      ', Info.FieldName);
      Writeln('Aláíró:    ', Info.SignerName);
      Writeln('Kibocsátó: ', Info.IssuerDN);
      Writeln('Algoritmus: ', Info.HashAlgorithm);
      Writeln('SubFilter: ', Info.SubFilter);
    end;
  finally
    Pdf.Free;
  end;
end;

A kriptográfiai ellenőrzés futtatása

A VerifyLoadedSignatureEx egyetlen hívással végrehajtja a fájlból betöltött dokumentum teljes ellenőrzését, és visszaadja a feltöltött adat rekordot: újra megnyitja a forrásfájlt, lehasheli a /ByteRange szegmenseket a SignerInfo kivonat-algoritmussal, összehasonlítja az eredményt a messageDigest aláírt attribútummal (RFC 5652 §5.4), majd RSA-ellenőrzést végez az aláíráson az aláírt attribútumok DER SET újrakódolásán; ha egy aláírás nem tartalmaz aláírt attribútumokat, az RSA-ellenőrzés közvetlenül a dokumentum hash-én fut le; a támogatott aláírások az RSA PKCS#1 v1.5 SHA-1, SHA-256, SHA-384 vagy SHA-512 kivonattal, ami lefedi a fősodorbeli aláíró eszközök által előállított adbe.pkcs7.detached és ETSI.CAdES.detached alszűrőket

var
  Status: THPDFSignatureVerifyStatus;
  Info: THPDFSignatureInfo;
begin
  Status := Pdf.VerifyLoadedSignatureEx(0, Info);
  case Status of
    svValid:
      if Info.CoversWholeDocument then
        Writeln('Érvényes; az aláírás a teljes fájlt lefedi')
      else
        Writeln('Érvényes; a fájlt az aláírás után kibővítették');
    svDigestMismatch:
      Writeln('A dokumentum bájtok megváltoztak az aláírás után');
    svSignatureInvalid:
      Writeln('Az RSA ellenőrzés sikertelen az aláírt attribútumokon');
    svUnsupportedAlgorithm:
      Writeln('Nem RSA kulcs vagy ismeretlen kivonat-algoritmus');
    svMalformed:
      Writeln('A CMS tároló nem értelmezhető');
    svSourceUnavailable:
      Writeln('Nincsenek forrásbájtok; használja a TStream túlterhelést');
  end;
end;

Két megvalósítási részletet érdemes tudni, mert ezek magyarázzák a kívülről rejtélyesnek tűnő hibákat; először is, az aláírt attribútumok ellenőrzése kényes a kódolásra: a fájlon belül az attribútumok [0] IMPLICIT címkével vannak ellátva, de az aláírást a DER SET OF formájukon számították ki, így az ellenőrző a hashelés előtt újra címkéz, pontosan úgy, ahogy az RFC 5652 §5.4 megköveteli; az a saját fejlesztésű ellenőrző, amely a bájtokat úgy hasheli, ahogy azok a fájlban megjelennek, elutasít minden helyesen aláírt dokumentumot; másodszor, a /Contents hagyományosan nullákkal van kitöltve egy fenntartott bájthozzárendelésig, így az ellenőrző az elemzés előtt levágja a DER blobot a külső SEQUENCE tényleges hosszára; a szemétnek tűnő záró nullák normálisak, nem sérülések; az ASN.1 értelmezési veszélyek ugyanezen családja a tanúsítványimportálási oldalon a PKCS#12 és ASN.1 biztonsági megerősítésről szóló cikk témája

Mit garantál valójában az érvényes aláírás?

Az svValid pontosan ezt jelenti: a /ByteRange által megnevezett bájtok hashe megegyezik azzal az értékkel, amelyet az aláíró aláírt, és az aláírás érvényes a CMS tárolóba beágyazott tanúsítvány nyilvános kulcsával; ez bájtintegritás plusz kulcskötés, és semmi más; a tanúsítványlánc és a bizalmi ellenőrzés kifejezetten kívül esik a HotPDF ellenőrzőjének hatókörén: nem járja be a láncot a gyökérig, nem ellenőrzi a visszavonást, és nem konzultál semmilyen bizalmi tárral; a módosított dokumentumot újra aláíró támadó önaláírt tanúsítványa svValid értékként fog ellenőrződni, mert a matematika belsőleg konzisztens; az, hogy az aláíró valóban az-e, akinek állítja magát, és hogy bízni kell-e benne, egy olyan politikai döntés, amely egy külön réteghez tartozik, legyen az a szervezete tanúsítvány-engedélyezési listája, a Windows tanúsítványtára vagy egy hitelesítésszolgáltató

A CoversWholeDocument jelző egy finomabb rést őriz; az aláírás mindig csak a saját /ByteRange-ét fedi le, és a PDF inkrementális frissítési mechanizmusa lehetővé teszi tartalom hozzáadását az aláírás után annak érvénytelenítése nélkül, ami szándékos, és így működnek a több-aláírásos munkafolyamatok; a jelzőt az ellenőrzés során számítják ki, és csak akkor igaz, ha a két szegmens plusz a /Contents rés átfogja a teljes fájlt; ha az svValid úgy érkezik, hogy a CoversWholeDocument hamis, az aláírt változat ép, de a fájl későbbi kiegészítéseket tartalmaz, és az, hogy ezek a kiegészítések mit változtattak meg, olyasmi, amit a munkafolyamatának kell eldöntenie, hogy tolerálja-e

A streamből betöltött és titkosított dokumentumoknak saját forrásbájtokra van szükségük

A paraméter nélküli VerifyLoadedSignature és VerifyLoadedSignatureEx attól függ, hogy a komponens emlékszik-e, melyik fájlból származik a dokumentum; ha streamből tölti be a dokumentumot, nincs megnyitható fájlnév; ugyanez vonatkozik a titkosított dokumentumoknál használt jelszavas újratöltési útra is, amely munkafolyamatot a HotPDF-fel végzett AES-256 PDF titkosításról szóló cikk ismerteti; mindkét esetben a fájl-alapú túlterhelések svSourceUnavailable értéket adnak vissza találgatás helyett; a megoldás a TStream túlterhelés, amely lehetővé teszi a nyers bájtok átadását bárhonnan, ahol tartja őket — egy meglévő fájlból, memóriapufferből vagy adatbázis-blobból

var
  Src: TFileStream;
  Status: THPDFSignatureVerifyStatus;
  Info: THPDFSignatureInfo;
begin
  // Streamből betöltött dokumentum: a komponens nem tárol forrás
  // fájlnevet, így a nyers bájtokat Önnek kell biztosítania
  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;

A nem ellenőrizhető dolgok jelentése

Az az ellenőrző, amely csak az „érvényes” és „érvénytelen” állapotokat ismeri, tévesen fogja jelenteni azokat a dokumentumokat, amelyeket csupán nem ért, ezért az állapot-felsorolás elválasztja azokat az eseteket, amelyeket a felhasználói felületének meg kell különböztetnie; az svDigestMismatch azt jelenti, hogy a dokumentum bájtok megváltoztak az aláírás után, ami a klasszikus módosítási jel; az svSignatureInvalid azt jelenti, hogy a bájtok hash-e helyes, de az RSA-ellenőrzés sikertelen volt, ami sérült vagy hamisított aláírási értékre utal; az svUnsupportedAlgorithm a korrekt válasz az ECDSA kulcsok és a fel nem ismert kivonatok esetében: az aláírás tökéletes lehet, a HotPDF egyszerűen nem tudja ellenőrizni, és annak „érvénytelenként” történő jelentése megbélyegezne egy egészséges dokumentumot; az svMalformed olyan CMS tárolót jelöl, amely egyáltalán nem értelmezhető; a kapu-stílusú ellenőrzéseknél a VerifyAllLoadedSignatures csak akkor ad vissza igaz értéket, ha legalább egy aláírásmező létezik, és mindegyikük svValid minősítést kap, ami kényelmes egyetlen logikai érték egy olyan beviteli folyamat számára, amely semmi kevesebbet nem fogad el

Az aláírás-ellenőrzés, a PAdES aláírás, az AES-256 titkosítás és a betöltött dokumentumot szerkesztő API mind ugyanabban a natív VCL könyvtárban érhető el Delphi és C++Builder rendszerekhez, külső DLL függőségek nélkül; a teljes funkciólista és a támogatott IDE verziók a HotPDF Component termékoldalán találhatók