HotPDF verifikuje digitalne potpise u učitanim PDF dokumentima putem tri metode klase THotPDF: GetLoadedSignatureInfo, VerifyLoadedSignature i VerifyLoadedSignatureEx, uvedenih u verziji v2.259.0. Komponenta ponovo izračunava sažetak (re-hashes) segmenata /ByteRange izvorne datoteke, proverava CMS atribut messageDigest i pokreće verifikaciju valjanosti RSA PKCS#1 v1.5 u odnosu na ugrađeni sertifikat potpisnika, vraćajući svValid kada su bajtovi dokumenta netaknuti
Scenario je svakodnevan, a ulozi nisu. Druga ugovorna strana vraća potpisani ugovor, vaš tok rada ga mora arhivirati i neko postavlja jedino važno pitanje: je li to dokument koji smo poslali, bajt po bajt, potpisan sertifikatom koji tvrdi? Odgovor na to u kodu je verifikacija 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 članak je o drugom smeru: PDF stiže već potpisan, a vi želite programsku presudu umesto snimka ekrana 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 definiše mehanizam: polje obrasca potpisa nosi rečnik čiji unos /Contents sadrži spremnik CMS SignedData (RFC 5652), a čije polje /ByteRange imenuje tačne regije datoteke koje potpis pokriva, prema §12.8.1. Polje je popis parova pomaka i dužine, u praksi dva segmenta: sve pre heksadecimalnog niza /Contents i sve nakon njega. Vrednost potpisa ne može pokriti samu sebe, pa se datoteka sažima oko te rupe
Taj dizajn ima posledicu koja oblikuje ceo API: verifikacija mora da izračuna sažetak izvornih serijalizovanih bajtova, tačno onako kako se nalaze na disku. Analizirani model objekta je beskoristan za to, jer ponovna serijalizacija čak i nepromenjenog dokumenta proizvodi drugačije bajtove. HotPDF stoga vrši verifikaciju 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 pre ikakve verifikacije
GetLoadedSignatureInfo analizira rečnik potpisa i njegov CMS spremnik bez diranja ijednog bajta dokumenta, što ga čini pravim prvim pozivom kada trebate samo prikazati ko je potpisao i kada. Polja potpisa indeksirana su od 0 prema redosledu polja obrasca, a GetLoadedSignatureFieldCount vam govori koliko ih postoji. Vraćeni zapis THPDFSignatureInfo nosi naziv polja, /SubFilter, uobičajeno ime sertifikata potpisnika (common name), prepoznatljiva imena (distinguished names) predmeta i izdavaoca, serijski broj, datume valjanosti, vreme potpisivanja (iz potpisanog atributa kada je prisutan, inače unos /M rečnika), naziv algoritma sažetka te nizove /Reason, /Location i /ContactInfo. Njegov član Status ostaje svNotVerified, što je poštena oznaka za "analizirano, nije verifikovano"
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 verifikacije
VerifyLoadedSignatureEx izvodi potpunu verifikaciju za dokument učitan iz datoteke i predaje nazad popunjeni zapis sa informacijama u jednom pozivu: ponovo otvara izvornu datoteku, računa sažetak segmenata /ByteRange pomoću algoritma sažetka SignerInfo, upoređuje rezultat sa potpisanim atributom messageDigest (RFC 5652 §5.4), a zatim RSA proverava potpis preko DER SET ponovnog kodiranja potpisanih atributa. Kada potpis ne nosi potpisane atribute, verifikacija RSA se umesto toga izvodi direktno 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 potfiltere 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 vredi znati jer objašnjavaju neuspehe koji izvana izgledaju tajanstveno. Prvo, provera 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 oblika, pa verifikator ponovo označava pre sažimanja, tačno onako kako to zahteva RFC 5652 §5.4. Ručno izrađen verifikator koji sažima bajtove onako kako se pojavljuju u datoteci odbaciće svaki ispravno potpisan dokument. Drugo, /Contents se uobičajeno popunjava nulama do rezervisane količine bajtova, pa verifikator skraćuje DER objekat na stvarnu dužinu njegove spoljne SEQUENCE pre analiziranja; nule na kraju koje izgledaju kao smeće normalna su pojava, a ne korupcija podataka. Istom porodicom opasnosti od analiziranja ASN.1 na strani uvoza sertifikata bavi se članak o sigurnosnom očvršćivanju PKCS#12 i ASN.1 u HotPDF-u
Šta zapravo garantuje važeći potpis?
svValid znači tačno ovo: bajtovi imenovani sa /ByteRange sažimaju se u vrednost koju je potpisnik potpisao, a potpis se verifikuje pod javnim ključem sertifikata ugrađenog u CMS spremnik. To je integritet bajtova plus povezivanje ključa i ništa više. Verifikacija lanca sertifikata i provera poverenja eksplicitno su van opsega za HotPDF-ov verifikator: on ne prolazi lanac do korena, ne proverava opoziv (revocation) niti konsultuje bilo koje spremište poverenja. Samopotpisani sertifikat napadača koji je ponovo potpisao izmenjeni dokument verifikovaće se kao svValid jer je matematika interno konzistentna. Je li potpisnik onaj za koga se izdaje i treba li mu iko verovati, odluka je politike koja pripada zasebnom sloju, bilo da se radi o popisu dopuštenih sertifikata vaše organizacije, spremištu sertifikata sistema Windows ili autoritetu za verifikaciju
Zastavica CoversWholeDocument čuva suptilniji jaz. Potpis uvek 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 funkcionišu tokovi rada sa više potpisa. Zastavica se izračunava tokom verifikacije i istinita je samo kada dva segmenta plus praznina /Contents obuhvataju celu datoteku. Kada stigne svValid sa zastavicom CoversWholeDocument postavljenom na false, potpisna revizija je netaknuta, ali datoteka sadrži kasnija dodavanja, a jesu li te izmene prihvatljive stvar je odluke vašeg toka rada
Dokumenti učitani iz toka i šifrovani dokumenti trebaju sopstvene izvorne bajtove
Pozivi VerifyLoadedSignature i VerifyLoadedSignatureEx bez parametara zavise od toga pamti li komponenta iz koje je datoteke dokument došao. Učitajte dokument iz toka i nema naziva datoteke za ponovno otvaranje; isto važi i nakon puta ponovnog učitavanja lozinke koji se koristi za šifrovane dokumente, toka rada opisanog u članku o AES-256 PDF šifrovanju sa HotPDF-om. U oba slučaja preopterećenja temeljena na datotekama vraćaju svSourceUnavailable umesto pogađanja. Rešenje je preopterećenje TStream, koje vam omogućuje da predate izvorne sirove bajtove sa mesta gde ste ih sačuvali — datoteku koju još uvek imate, memorijski bafer ili objekat 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 verifikovati
Verifikator koji zna samo za "valjano" i "nevaljano" pogrešno će prijaviti dokumente koje jednostavno ne razume, pa nabrajanje statusa razdvaja slučajeve koje bi vaše korisničko sučelje trebalo da razlikuje. svDigestMismatch znači da su se bajtovi dokumenta promenili nakon potpisivanja, što je klasični signal neovlašćenog menjanja (tampering). svSignatureInvalid znači da se bajtovi ispravno sažimaju, ali RSA verifikacija nije uspela, što upućuje na oštećenu ili krivotvorenu vrednost 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 verifikovati, a prijavljivanje toga kao "nevaljanog" naštetilo bi ispravnom dokumentu. svMalformed označava CMS spremnik koji se uopšte nije mogao analizirati. Za verifikaciju na ulaznim vratima (gate-style checks), VerifyAllLoadedSignatures vraća true samo kada postoji barem jedno polje potpisa i svako od njih se verifikuje kao svValid, što je praktičan jedinstveni boolean za pipeline unosa arhive koji odbija sve manje od toga
Verifikacija potpisa, PAdES potpisivanje, AES-256 šifrovanje i API za uređivanje učitanih dokumenata dolaze u istoj izvornoj VCL biblioteci za Delphi i C++Builder, bez spoljnih DLL zavisnosti; ceo popis funkcija i podržane verzije IDE-a nalaze se na stranici proizvoda HotPDF Component