Dobili ste potpisan PDF i morate u svom pregledaču da prikažete ko ga je potpisao, kada je potpisan, da li potpis pokriva celu datoteku i koliko je blizu dugoročne usaglašenosti. Komponenta PDFium Component za Delphi i Lazarus odgovara na sva četiri pitanja pomoću poziva samo za čitanje: FPDF_GetSignatureCount i familija FPDFSignatureObj_* izlažu rečnik potpisa, a metod TPdf.ValidatePades klasifikuje PAdES osnovni nivo. Ovo je prvi od tri članka o PDF potpisima pomoću PDFium-a; dva koja slede pokrivaju kreiranje B-B potpisa i dodavanje dugoročnih vremenskih žigova (timestamps). Ipak, jedna granica se mora postaviti na samom početku: sve što je ovde opisano je inspekcija, a čitanje onoga što potpis tvrdi je sasvim drugačiji posao od provere njegove kriptografije ili odlučivanja o tome da li verujete potpisniku
Zašto rečnik PDF potpisa nije samo skup bajtova
PDF potpis je rečnik (dictionary), a ne neprozirni prilog, a njegova dva najvažnija unosa govore vam koliki deo datoteke je zapravo zaštićen. Standard ISO 32000-1 §12.8 definiše rečnik potpisa sa unosima /ByteRange i /Contents. Unos /Contents drži heksadecimalno kodiranu CMS SignedData strukturu (RFC 5652), kriptografski koverat koji nosi sertifikat potpisnika, potpisane atribute i samu vrednost potpisa. Unos /ByteRange je deo koji programeri često potcenjuju: to je niz od dva raspona pomeraj-dužina koji zajedno pokrivaju celu datoteku osim heksadecimalnog stringa /Contents. Taj procep je upravo mesto gde leže bajtovi potpisa, a dva opsega sa obe strane su upravo ono na šta se potpis obavezuje
Dizajn ByteRange je ono što omogućava reviziju inkrementalnog čuvanja. Pošto potpisnik ne može da hešira bajtove potpisa koji još ne postoje, datoteka je podeljena oko čuvara mesta /Contents i sve ostalo je heširano u potpis. Potpis čiji ByteRange ne doseže kraj datoteke je znak upozorenja: sadržaj dodat nakon pokrivenog opsega, kroz kasniju inkrementalnu ispravku, ne bi narušio potpis iako menja ono što čitalac vidi. Dakle, prva stvar koju ozbiljan inspektor proverava nije ko je potpisao, već da li potpis pokriva bajtove koje navodno potvrđuje
Čitanje rečnika potpisa pomoću PDFium API-ja samo za čitanje
PDFium komponenta prikazuje rečnik potpisa kroz dva člana samo za čitanje: SignatureCount i zapis Signature[Index]. Ispod haube, oni pozivaju FPDF_GetSignatureCount, FPDF_GetSignatureObject i pristupne funkcije FPDFSignatureObj_* za /SubFilter, /ByteRange, /Contents, /Reason i vreme potpisivanja. "Samo za čitanje" je ključna fraza ovde: PDFium može da nabroji i čita potpise, ali nema API za njihovo kreiranje ili upisivanje, zbog čega je strana potpisivanja u ovoj seriji implementirana od strane same biblioteke, a ne od strane PDFium-a
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); // signer date string, e.g. D:20260708120000+02'00'
Writeln('Reason : ', Sig.Reason);
Writeln('CMS length: ', Length(Sig.Content)); // raw DER SignedData taken from /Contents
Writeln('DocMDP : ', Sig.Permission); // 0 = no certification, 1..3 = MDP level
end;
finally
Pdf.Free;
end;
end;
Svaki zapis TPdfSignature se direktno preslikava na rečnik. Encoding je zapravo /SubFilter, polje koje najviše govori o dijagnostici, jer imenuje rukovaoca potpisom i odmah odvaja moderan ETSI.CAdES.detached potpis od zastarelog ili zabranjenog. Time je vreme potpisivanja koje je deklarisao autor u vidu PDF datuma, što je tvrdnja potpisnika, a ne provereno vreme. Content je sirovi CMS SignedData, a Permission izlaže nivo sertifikacije DocMDP (0 za običan potpis odobrenja, 1 do 3 za sertifikacioni potpis koji zaključava kasnije izmene). Jedino polje koje ovaj zapis ne prikazuje je parsirani ByteRange, a taj izostanak je nameran, jer metod ValidatePades umesto vas radi aritmetiku pokrivenosti ByteRange-a, tako da je ne morate raditi ručno
Koja je razlika između PAdES B-B, B-T, B-LT i B-LTA nivoa?
Četiri osnovna PAdES nivoa čine lestvicu od minimalno važećeg potpisa do onog izgrađenog da preživi decenije arhiviranja, pri čemu svaki nivo striktno sadrži onaj ispod sebe. Standard ETSI EN 319 142-1 ih definiše kao B-B, B-T, B-LT i B-LTA. B-B (Basic - osnovni) je potpis plus obavezni potpisani atributi i ništa više. B-T (Timestamp - vremenski žig) dodaje provereni RFC 3161 vremenski žig preko potpisa, tako da trenutak potpisivanja potvrđuje autoritet za izdavanje vremenskih žigova (Time-stamping Authority), umesto da ga tvrdi sat potpisnika. B-LT (Long-Term - dugoročni) ugrađuje materijale za validaciju — lanac sertifikata i opciono OCSP ili CRL odgovore — unutar datoteke, tako da se potpis i dalje može validirati godinama kasnije kada infrastruktura izdavaoca nestane. B-LTA (Long-Term with Archive timestamp - dugoročni sa arhivskim žigom) obmotava vremenski žig dokumenta oko tog materijala, štiteći same dugoročne podatke i dajući vam tačku za ponovno stavljanje žiga pre nego što osnovna kriptografija zastari
Praktično tumačenje se odnosi na vremenski horizont. B-B potpis odgovara na pitanje "da li je neko ovo potpisao". B-T odgovara na "i kada, dokazivo". B-LT odgovara na "i da li to još uvek mogu da proverim nakon što sertifikati isteknu". B-LTA odgovara na "i da li će ta provera i dalje važiti za dvadeset godina". Regulatorni profili biraju prečku: mnogi e-invoicing i eIDAS konteksti zahtevaju najmanje B-T, dok arhivski mandati zahtevaju B-LT ili B-LTA. Poznavanje prečke koju dokument zapravo doseže, pre nego što ga prihvatite ili odbijete, jeste cela suština koraka inspekcije
Detektovanje osnovnog nivoa pomoću TPdf.ValidatePades
PDFium komponenta svodi celo pitanje nivoa na jedan poziv. Metod TPdf.ValidatePades vraća zapis TPadesValidationResult čije je polje Level zapravo TPadesLevel — plNone, plUnknown, plB_B, plB_T, plB_LT ili plB_LTA — zajedno sa skupom problema, brojem potpisa i brojem vremenskih žigova dokumenta. Nivo se zaključuje monotonim redosledom: validator najpre utvrđuje B-B, zatim prelazi na B-T ako je prisutan vremenski žig potpisa ili vremenski žig dokumenta, na B-LT ako katalog sadrži /DSS sa sertifikatima i marker /Extensions /ESIC Nivoa 1, i na B-LTA ako su prisutni i vremenski žig dokumenta i marker ESIC Nivoa 2. Dva pomoćnika čine rezultat upotrebljivim: IsCompliant je True samo kada nivo dostigne najmanje B-B i skup problema je prazan, a IsCompliantAt vam omogućava da zahtevate minimalni nivo politike kao što 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 (basic)');
plB_T: Writeln('PAdES B-T (trusted timestamp)');
plB_LT: Writeln('PAdES B-LT (long-term material embedded)');
plB_LTA: Writeln('PAdES B-LTA (archive timestamp)');
end;
Writeln('Signatures : ', R.SignatureCount);
Writeln('DocTimeStamps: ', R.DocTimeStampCount);
if R.IsCompliantAt(plB_T) then
Writeln('Meets the B-T policy floor')
else
Writeln('Below the required B-T level');
finally
Pdf.Free;
end;
end;
Zašto je adbe.pkcs7.sha1 zabranjen SubFilter?
Zato što je SHA-1 kompromitovan, a rukovalac adbe.pkcs7.sha1 ga ugrađuje u sebe. Taj SubFilter unapred hešira dokument pomoću SHA-1 pre nego što ga obmota u PKCS#7, a SHA-1 je godinama ranjiv na kolizije, tako da ga standard EN 319 142-1 član 6.3 izričito zabranjuje za osnovni potpis. Metod ValidatePades podiže grešku ppeiForbiddenSubFilter kada vidi adbe.pkcs7.sha1 ili adbe.x509.rsa_sha1, i podiže ppeiBadDigestAlgorithm kada sam CMS koristi MD5 ili SHA-1 kao sažetak poruke (član 6.2.1). To su dve odvojene provere koje hvataju istu vrstu slabosti na dva različita sloja
Skup problema ima ukupno 26 članova, a oni sa kojima ćete se najčešće sresti grupisani su oko strukture i pokrivenosti. ppeiByteRangeNotCoveringFile je provera pokrivenosti koja je ranije opisana. ppeiForbiddenCertKey se aktivira kada rečnik potpisa sadrži unos /Cert, što PAdES zabranjuje jer lanac umesto toga mora da živi unutar CMS-a SignedData.certificates. Zastavice ppeiMissingSigningCertificate, ppeiMissingContentType i ppeiMissingMessageDigest označavaju obavezne potpisane atribute koji nedostaju, dok ppeiDetachedContentViolation hvata potpis koji pogrešno ugrađuje potpisani sadržaj umesto da ga odvoji. Nabrajanje ovog skupa pretvara obično odbijanje u dijagnozu koju možete da zabeležite
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;
Šta ValidatePades ne proverava
Metod ValidatePades proverava strukturu, a ne poverenje, i mešanje ta dva je opasna greška. Rezultat plB_LTA znači da dokument sadrži ispravno oblikovan B-LTA potpis sa svim propisanim atributima, materijalima i vremenskim žigovima na pravim mestima — to ne znači da je potpis kriptografski validan, da se lanac sertifikata povezuje sa korenom (root) kojem verujete ili da nijedan sertifikat u lancu nije opozvan. Validator namerno ne vrši kriptografsku proveru: ne računa ponovo potpis preko ByteRange-a, ne gradi niti procenjuje lanac poverenja i ne proverava OCSP ili CRL status opoziva. Ta podela je namerna i korisna, jer je strukturna inspekcija brza, potpuno deterministička i ne zahteva ključeve, mrežu niti kriptografiju platforme, pa tako ValidatePades radi identično na sistemima Windows, Linux i macOS kao čist Pascal preko bajtova datoteke. Validacija lanca poverenja je, nasuprot tome, neodvojiva od politike — kojim korenima verujete, kako preuzimate opozive, kolika je vaša tolerancija na odstupanje vremenskog žiga — i pripada kasnijoj fazi koja zavisi od skladišta sertifikata platforme. Stoga tretirajte prolazak provere ValidatePades kao neophodnu potvrdu da je potpis ispravno oblikovan, a zatim structurally ispravan potpis predajte stvarnoj kriptografskoj verifikaciji pre nego što se oslonite na njega
Taj strukturni prolaz je pravo mesto za početak i prirodno se uparuje sa širim proverama samo za čitanje opisanih u članku o reviziji bezbednosnih rizika u PDF-u pomoću PDFium komponente i radom na usaglašavanju formata kao što je validacija PDF/X dokumenata spremnih za štampu. Kada možete da čitate i klasifikujete potpis, sledeći korak je njegovo kreiranje: drugi članak u ovoj seriji pokriva potpisivanje PDF-ova sa PAdES B-B, a treći proširuje to na proverene vremenske žigove i dugoročne B-LT i B-LTA potpise. Inspekcija potpisa samo za čitanje i ValidatePades klasifikator prikazani ovde deo su komponente PDFium Component za Delphi, C++Builder i Lazarus