Tehnički članak

Provjera digitalnih potpisa PDF-a u Delphiju pomoću HotPDF-a

HotPDF provjerava digitalne potpise u učitanim PDF dokumentima putem tri metode klase THotPDF: GetLoadedSignatureInfo, VerifyLoadedSignature i VerifyLoadedSignatureEx, uvedenih u verziji v2.259.0. Komponenta ponovno izračunava sažetak (re-hashes) segmenata /ByteRange izvorne datoteke, provjerava CMS atribut messageDigest i pokreće provjeru valjanosti RSA PKCS#1 v1.5 u odnosu na ugrađeni certifikat potpisnika, vraćajući svValid kada su bajtovi dokumenta netaknuti

Scenarij je svakodnevan, a ulozi nisu. Druga ugovorna strana vraća potpisani ugovor, vaš tijek rada ga mora arhivirati i netko postavlja jedino važno pitanje: je li to dokument koji smo poslali, bajt po bajt, potpisan certifikatom koji tvrdi? Odgovor na to u kodu je provjera valjanosti potpisa; strana potpisivanja, odnosno stvaranje i ugradnja PAdES potpisa, pokrivena je u pratećem članku o stvaranju PAdES digitalnih potpisa pomoću HotPDF-a. Ovaj je članak o drugom smjeru: PDF stiže već potpisan, a vi želite programsku presudu umjesto snimke zaslona Acrobatove zelene kvačice

Kako potpisani PDF dokazuje da se u njega nije diralo?

Potpis PDF-a štiti određene raspone bajtova datoteke, a ne apstraktni pojam "dokumenta". ISO 32000-1 §12.8 definira mehanizam: polje obrasca potpisa nosi rječnik čiji unos /Contents sadrži spremnik CMS SignedData (RFC 5652), a čije polje /ByteRange imenuje točne regije datoteke koje potpis pokriva, prema §12.8.1. Polje je popis parova pomaka i duljine, u praksi dva segmenta: sve prije heksadecimalnog niza /Contents i sve nakon njega. Vrijednost potpisa ne može pokriti samu sebe, pa se datoteka sažima oko te rupe

Taj dizajn ima posljedicu koja oblikuje cijeli API: provjera valjanosti mora izračunati sažetak izvornih serijaliziranih bajtova, točno onako kako se nalaze na disku. Analizirani model objekta je beskoristan za to, jer ponovna serijalizacija čak i nepromijenjenog dokumenta proizvodi drugačije bajtove. HotPDF stoga vrši provjeru valjanosti u odnosu na izvornu datoteku iz koje je dokument učitan ili u odnosu na TStream sirovih bajtova koje isporučite, nikada u odnosu na njegovu reprezentaciju u memoriji

Čitanje metapodataka potpisa prije ikakve provjere

GetLoadedSignatureInfo analizira rječnik potpisa i njegov CMS spremnik bez diranja ijednog bajta dokumenta, što ga čini pravim prvim pozivom kada trebate samo prikazati tko je potpisao i kada. Polja potpisa indeksirana su od 0 prema redoslijedu polja obrasca, a GetLoadedSignatureFieldCount vam govori koliko ih postoji. Vraćeni zapis THPDFSignatureInfo nosi naziv polja, /SubFilter, uobičajeno ime certifikata potpisnika (common name), prepoznatljiva imena (distinguished names) predmeta i izdavatelja, serijski broj, datume valjanosti, vrijeme potpisivanja (iz potpisanog atributa kada je prisutan, inače unos /M rječnika), naziv algoritma sažetka te nizove /Reason, /Location i /ContactInfo. Njegov član Status ostaje svNotVerified, što je poštena oznaka za "analizirano, nije provjereno"

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;

Pokretanje kriptografske provjere

VerifyLoadedSignatureEx izvodi potpunu provjeru valjanosti za dokument učitan iz datoteke i predaje natrag popunjeni zapis s informacijama u jednom pozivu: ponovno otvara izvornu datoteku, računa sažetak segmenata /ByteRange pomoću algoritma sažetka SignerInfo, uspoređuje rezultat s potpisanim atributom messageDigest (RFC 5652 §5.4), a zatim RSA provjerava potpis preko DER SET ponovnog kodiranja potpisanih atributa. Kada potpis ne nosi potpisane atribute, provjera RSA se umjesto toga izvodi izravno preko sažetka dokumenta. Podržani potpisi su RSA PKCS#1 v1.5 sa sažecima SHA-1, SHA-256, SHA-384 ili SHA-512, što pokriva potfiltre adbe.pkcs7.detached i ETSI.CAdES.detached koje proizvode uobičajeni alati za potpisivanje

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;

Dva detalja implementacije vrijedi znati jer objašnjavaju neuspjehe koji izvana izgledaju tajanstveno. Prvo, provjera potpisanih atributa izbirljiva je u pogledu kodiranja: unutar datoteke atributi su označeni kao [0] IMPLICIT, ali potpis je izračunat preko njihovog DER SET OF obrika, pa verifikator ponovno označava prije sažimanja, točno onako kako to zahtijeva RFC 5652 §5.4. Ručno izrađen verifikator koji sažima bajtove onako kako se pojavljuju u datoteci odbacit će svaki ispravno potpisan dokument. Drugo, /Contents se uobičajeno popunjava nulama do rezervirane količine bajtova, pa verifikator skraćuje DER objekt na stvarnu duljinu njegove vanjske SEQUENCE prije analiziranja; nule na kraju koje izgledaju kao smeće normalna su pojava, a ne korupcija podataka. Istom obitelji opasnosti od analiziranja ASN.1 na strani uvoza certifikata bavi se članak o sigurnosnom očvršćivanju PKCS#12 i ASN.1 u HotPDF-u

Što zapravo jamči važeći potpis?

svValid znači točno ovo: bajtovi imenovani s /ByteRange sažimaju se u vrijednost koju je potpisnik potpisao, a potpis se verificira pod javnim ključem certifikata ugrađenog u CMS spremnik. To je integritet bajtova plus povezivanje ključa i ništa više. Provjera lanca certifikata i provjera povjerenja eksplicitno su izvan opsega za HotPDF-ov verifikator: on ne prolazi lanac do korijena, ne provjerava opoziv (revocation) niti konzultira bilo koje spremište povjerenja. Samopotpisani certifikat napadača koji je ponovno potpisao izmijenjeni dokument provjerit će se kao svValid jer je matematika interno konzistentna. Je li potpisnik onaj za kojeg se izdaje i treba li mu itko vjerovati, odluka je politike koja pripada zasebnom sloju, bilo da se radi o popisu dopuštenih certifikata vaše organizacije, spremištu certifikata sustava Windows ili autoritetu za provjeru

Zastavica CoversWholeDocument čuva suptilniji jaz. Potpis uvijek pokriva samo svoj /ByteRange, a PDF-ov mehanizam inkrementalnog ažuriranja dopušta dodavanje sadržaja nakon potpisa bez njegovog poništavanja, što je po dizajnu i tako funkcioniraju tijekovi rada s više potpisa. Zastavica se izračunava tijekom provjere valjanosti i istinita je samo kada dva segmenta plus praznina /Contents obuhvaćaju cijelu datoteku. Kada stigne svValid sa zastavicom CoversWholeDocument postavljenom na false, potpisna revizija je netaknuta, ali datoteka sadrži kasnija dodavanja, a jesu li te izmjene prihvatljive stvar je odluke vašeg tijeka rada

Dokumenti učitani iz toka i šifrirani dokumenti trebaju vlastite izvorne bajtove

Pozivi VerifyLoadedSignature i VerifyLoadedSignatureEx bez parametara ovise o tome pamti li komponenta iz koje je datoteke dokument došao. Učitajte dokument iz toka i nema naziva datoteke za ponovno otvaranje; isto vrijedi i nakon puta ponovnog učitavanja lozinke koji se koristi za šifrirane dokumente, tijeka rada opisanog u članku o AES-256 PDF šifriranju s HotPDF-om. U oba slučaja preopterećenja temeljena na datotekama vraćaju svSourceUnavailable umjesto pogađanja. Rješenje je preopterećenje TStream, koje vam omogućuje da predate izvorne sirove bajtove s mjesta gdje ste ih pohranili — datoteku koju još uvijek imate, memorijski međuspremnik ili objekt baze podataka (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;

Prijavljivanje onoga što ne možete provjeriti

Verifikator koji zna samo za "valjano" i "nevaljano" pogrešno će prijaviti dokumente koje jednostavno ne razumije, pa nabrajanje statusa razdvaja slučajeve koje bi vaše korisničko sučelje trebalo razlikovati. svDigestMismatch znači da su se bajtovi dokumenta promijenili nakon potpisivanja, što je klasični signal neovlaštenog mijenjanja (tampering). svSignatureInvalid znači da se bajtovi ispravno sažimaju, ali RSA provjera nije uspjela, što upućuje na oštećenu ili krivotvorenu vrijednost potpisa. svUnsupportedAlgorithm je pošten odgovor za ECDSA ključeve i neprepoznate sažetke: potpis može biti savršeno dobar, ali ga HotPDF jednostavno ne može provjeriti, a prijavljivanje toga kao "nevaljanog" naštetilo bi ispravnom dokumentu. svMalformed označava CMS spremnik koji se uopće nije mogao analizirati. Za provjere na ulaznim vratima (gate-style checks), VerifyAllLoadedSignatures vraća true samo kada postoji barem jedno polje potpisa i svako od njih se provjeri kao svValid, što je praktičan jedinstveni boolean za cjevovod unosa arhive koji odbija sve manje od toga

Provjera potpisa, PAdES potpisivanje, AES-256 šifriranje i API za uređivanje učitanih dokumenata dolaze u istoj izvornoj VCL knjižnici za Delphi i C++Builder, bez vanjskih DLL ovisnosti; cijeli popis značajki i podržane verzije IDE-a nalaze se na stranici proizvoda HotPDF Component