PDFium VCL tretira CMS verifikaciju kao zamenljiv backend iza IPdfCmsVerifier interfejsa, pa PAdES validator može raditi na Windows-u kroz CryptoAPI, na macOS-u kroz Keychain, i bilo gde gde ima OpenSSL-a kroz ConfigureSslCmsVerifier. Interfejs je mali. Tri OpenSSL ponašanja ispod njega proizvode samouverene pogrešne odgovore ako ga implementirate naivno
Motivacija je jasna čim Delphi aplikacija napusti Windows. Validacija potpisa je jedna od retkih oblasti gde platformski crypto stek nije implementacioni detalj: on odlučuje koji su sertifikati pouzdani, koji algoritmi postoje, i šta opoziv znači. Hard-kodujte jednog pa se kod ne portuje. Apstrahujte ga loše pa svaka platforma prijavljuje drugačije oblikovan odgovor koji pozivalac ne može porediti
Šta apstrakcija zapravo mora da nosi
Dva oblika verifikacije i tri nezavisne presude. PDF potpis je odvojen: potpisani sadržaj su dva bajt opsega s obe strane /Contents rupe, pa VerifyDetached uzima dva segmenta umesto jednog bafera. Timestamp token je zakačen, nosi svoj sadržaj, pa VerifyAttached uzima samo DER
Rezultat se deli u tri statusa jer odgovaraju na tri različita pitanja i mogu se razilaziti. SignatureStatus kaže da li su bajtove potpisali ključem iz sertifikata potpisivača. TrustStatus kaže da li taj sertifikat pravi lanac do nečega što smatrate pouzdanim. RevocationStatus kaže da li je sertifikat bio važeći u relevantnom trenutku. Dokument sa matematički savršenim potpisom od sertifikata o kome nikada niste čuli je validan, nepouzdan i nepoznat, i sabijanje toga u jedan boolean je način na koji validatori završe lažući korisnike
uses
FPdfCrypto, FPdfCryptoSsl;
var
Options: TPdfCmsVerifyOptions;
begin
if not SslAvailable then
raise Exception.Create('libcrypto not usable: ' + SslMissingSymbols);
ConfigureSslTrustAnchors(LoadCorporateRoots); // DER, može biti prazno
ConfigureSslCrls(LoadFreshCrls); // DER, može biti prazno
ConfigureSslCmsVerifier; // instalira backend
Writeln('backend : ', PadesCmsVerificationBackendName);
Writeln('library : ', SslLibraryPath, ' ', SslLibraryVersion);
Writeln('ABI : ', SslAbiLayout); // ulong=<n> long=<n>
Options := TPdfCmsVerifyOptions.Default;
Options.CheckRevocation := True;
Options.CollectChainCertificates := True;
end;
SslAbiLayout deluje kao radoznalost, a nije. Svaki OpenSSL kod greške i svaka store zastavica prelazi granicu kao C unsigned long, koji je četiri bajta na Windows-u i osam na Linux-u i macOS-u. Deklarišite ga kao fiksni 32-bitni tip pa kod radi na Windows-u, a onda tiho čita pola vrednosti na LP64. Prijavljivanje pretpostavljenih širina kao stringa na koji možete asertovati u testu pretvara celu klasu platformskog ABI drifta u proveru od jedne linije. Svako ko je prošao kroz isti problem sa CK_ULONG u PKCS#11 vezivanju prepoznaće ga odmah; ta priča je u članku o PKCS#11 struct pakovanju i CK_ULONG širini
Zašto drugi prolaz verifikacije vidi prazan sadržaj?
Zato što CMS_verify čita odvojeni content BIO do kraja datoteke, i BIO koji je pročitan vam se ne vraća unazad. Verifikacija u dva prolaza je razumna dizajnerska odluka: prvo samo kriptografski potpis uz potisnutu procenu lanca, pa puna procena, i ona pada na neobično obmanjujući način ako oba prolaza dele jedan BIO
Drugi prolaz dobija nula bajtova sadržaja. U detached režimu to nije greška, jer je prazan bafer sadržaja legalan ulaz. Sažetak jednostavno se ne poklapa, i otkaz se pokazuje kao neuspeh gradnje lanca umesto kao neuspeh sadržaja, što vas šalje da pregledate sertifikate i trust store-ove dok je stvarni problem pozicija u toku. Ponovo izgradite memory BIO sa BIO_new_mem_buf za svaki prolaz. Košta jednu alokaciju i uklanja mogućnost u potpunosti
Šta no-verify zastavica potiskuje, a šta ne
CMS_NO_SIGNER_CERT_VERIFY potiskuje procenu lanca, a ne pretragu sertifikata potpisivača. Iznutra OpenSSL razrešava i kači sertifikate potpisivača pre nego što konsultuje zastavicu, pa posle prvog prolaza koji nosi tu zastavicu potpisivač je već dostupan i njegovi identifikatori algoritma mogu se odmah čitati. Nema potrebe vršiti drugu punu verifikaciju samo da biste dobili sertifikat potpisivača, što ime zastavice navodi da pretpostavite
Uz to ide jedno pravilo vlasništva. Referenca na potpisivača pripada CMS strukturi i ne sme se oslobađati nezavisno. Važi dok god važi struktura, i njeno oslobađanje proizvodi korupciju čiji se simptom pojavi negde sasvim drugde, obično tokom čišćenja nepovezanog objekta
Zašto uključivanje CRL provere odbija svaki potpis?
Zato što OpenSSL proverava CRL-ove samo naspram onoga što store već drži i ništa sam ne dovlači. Ne prati CRL distribution point-ove i ne govori OCSP. Postavite X509_V_FLAG_CRL_CHECK na store bez ijednog CRL-a i svaki lanac pada sa nemogućnošću da se dobije sertifikatski CRL. Rezultat izgleda kao provera opoziva koja radi i nalazi probleme. To je provera opoziva koja nikada nije ni proradila
Backend zato postavlja zastavicu samo kad ConfigureSslCrls stvarno snabde barem jedan CRL. Bez njega, RevocationStatus se vraća kao pcvsUnsupported, što je iskrena izjava da pitanje nije odgovoreno. Iz istog razloga OnlineRetrieval nema efekta na ovaj backend i nijedan pcvstOnlineRetrieval kontrolni punkt se ne emituje: nema putanje dovlačenja sa koje bi se prijavio napredak
Ovo je dizajnerski stav vredan odbrane uopšte. Validator koji ne može da proveri opoziv treba to da kaže. Prijavljivanje neproverenog sertifikata kao neopozvanog je najčešći pojedinačni način na koji alatke za validaciju potpisa navode svoje korisnike na pogrešan put, i to je baš klasa zabune istražena u članku o tome zašto validatori odbijaju PAdES potpise
// Kontrolne tačke omogućavaju UI-ju da prikaže koja faza radi, i govore vam
// koje faze backend stvarno izvršava
type
TSignatureProbe = class
procedure Checkpoint(Stage: TPdfCmsVerifyStage);
end;
procedure TSignatureProbe.Checkpoint(Stage: TPdfCmsVerifyStage);
begin
case Stage of
pcvstCryptographicSignature: Status('checking the signature');
pcvstChainBuild: Status('building the certificate chain');
pcvstOnlineRetrieval: Status('fetching validation data');
pcvstRevocationCheck: Status('checking revocation');
end;
end;
// Čitajte tri presude odvojeno; dozvoljeno im je da se ne slažu
if Result.SignatureStatus = pcvsValid then
case Result.TrustStatus of
pcvsValid: Report('signed and trusted');
pcvsInvalid: Report('signed, chain rejected');
pcvsUnsupported,
pcvsIndeterminate: Report('signed, trust not established');
end;
if Result.RevocationStatus = pcvsUnsupported then
Report('revocation was not checked on this backend');
Vezivanje za biblioteku koju ne možete prikovati
OpenSSL je preimenovao svoje pristupnike steka između 1.0 i 1.1, pa ista logička funkcija ima dva moguća izvezena imena zavisno od build-a koji domaćin slučajno ima. Vezivanje prvo razrešava novije ime i pada nazad na starije, i beleži nedostajući simbol tek kad se nijedno ne razreši. To je pravi oblik za svako dinamičko vezivanje naspram biblioteke koju ne isporučujete: dajte prednost trenutnim imenima, trpite istorijska, i prijavljujte samo stvarni nedostatak
SslMissingSymbols je ono što neuspešno učitavanje pretvara u dijagnostikovljiv događaj. Neprazan rezultat na domaćinu koji očigledno ima instaliran libcrypto znači da je instalirana verzija starija od API-ja kome ovaj build teži, što je sasvim drugačiji razgovor o podršci od biblioteke koja nedostaje. ConfigureSslLibraryPath pokriva drugi uobičajeni slučaj, domaćina sa nekoliko OpenSSL build-ova gde onaj na podrazumevanoj search putanji nije onaj koji želite
Biranje backend-a po platformi
Praktičan aranžman je izabrati pri pokretanju i zabeležiti koji je odgovorio. Na Windows-u, platformski backend se integriše sa sertifikatskim store-ovima kojima preduzeće već upravlja, što je obično ono što želite. Na macOS-u Keychain backend odgovara istom rasuđivanju i opisan je u članku o verifikaciji potpisa sa SecTrust-om na macOS-u. OpenSSL je prenosiva opcija, i to je takođe pravi izbor kad vam treba politika validacije identična preko platformi, a ne ona koja prati trust store svake platforme
Koji god instalirate, logujte PadesCmsVerificationBackendName pored svake presude koju zabeležite. Sačuvan rezultat validacije bez backend-a koji ga je proizveo ne može se kasnije reprodukovati, jer tri statusne vrednosti znače suptilno različite stvari zavisno od toga koji stek je odgovorio. Sloj pregleda potpisa iznad svega ovoga, uključujući kako se prijavljuju PAdES nivoi, pokriven je u članku o pregledanju PDF digitalnih potpisa i PAdES nivoa
Sve to stiže kao izvor uz PDFium Delphi komponentu, što ovde znači više nego obično: za validator potpisa, mogućnost da pročitate tačno koje zastavice backend postavlja i koje provere preskače nije ukras, to je jedini način da znate šta zeleni ček u vašoj aplikaciji stvarno tvrdi