PDFium VCL tretira CMS verifikaciju kao zamjenjivi backend iza IPdfCmsVerifier interfacea, pa PAdES validator može trčati na Windowsima kroz CryptoAPI, na macOS-u kroz Keychain, i bilo gdje gdje je OpenSSL prisutan kroz ConfigureSslCmsVerifier. Interface je malen. Tri OpenSSL ponašanja ispod njega proizvode samouvjerene, krive odgovore ako ga implementirate naivno
Motivacija je jasna čim Delphi aplikacija napusti Windows. Validacija potpisa je jedno od područja gdje platformski crypto stack nije implementacijski detalj: odlučuje koji su certifikati vjerodostojni, koji algoritmi postoje, i što opoziv znači. Hardkodirajte jedan i kod se ne portira. Apstrahirajte ga loše i svaka platforma javlja drugačije oblikovan odgovor koji pozivatelj ne može usporediti
Što apstrakcija stvarno mora nositi
Dva oblika verifikacije i tri neovisne presude. PDF potpis je odvojen: potpisani sadržaj su dva byte raspona s obje strane /Contents rupe, pa VerifyDetached uzima dva segmenta umjesto jednog buffera. Timestamp token je priložen, noseći vlastiti sadržaj, pa VerifyAttached uzima samo DER
Rezultat se dijeli u tri statusa jer odgovaraju na tri različita pitanja i mogu se razilaziti. SignatureStatus kaže jesu li bajtove potpisali ključem u certifikatu potpisnika. TrustStatus kaže se taj certifikat lančano veže na nešto čemu vjerujete. RevocationStatus kaže je li certifikat još bio važeći u relevantno vrijeme. Dokument s matematički savršenim potpisom od certifikata za koji nikad niste čuli je valjan, nepouzdan i nepoznat, i sažeti to 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, smije biti prazno
ConfigureSslCrls(LoadFreshCrls); // DER, smije 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 izgleda kao kuriozitet i nije. Svaki OpenSSL error code i svaka store zastavica prelazi granicu kao C unsigned long, koji je četiri bajta na Windowsima i osam na Linuxu i macOS-u. Deklarirajte ga kao fiksni 32-bit tip i kod radi na Windowsima, pa tiho čita pola vrijednosti na LP64. Javiti pretpostavljene širine kao string na kojem možete assertati u testu pretvara cijelu klasu platformskog ABI drifta u provjeru jedne linije. Tko je radio kroz isti problem s CK_ULONGom u PKCS#11 vezivanju prepoznat će ga odmah; ta je priča u PKCS#11 struct pakiranju i širini CK_ULONG-a
Zašto drugi prolaz verifikacije vidi prazan sadržaj?
Jer CMS_verify čita odvojeni content BIO do kraja datoteke, i BIO koji je pročitan nije namotan natrag za vas. Verificirati u dva prolaza je razumna konstrukcija, najprije samo kriptografski potpis s potisnutom evaluacijom lanca, pa puna evaluacija, i pade na neobično obmanjujući način ako oba prolaza dijele jedan BIO
Drugi prolaz dobiva nula bajtova sadržaja. U odvojenom modu to nije greška, jer je prazan content buffer legalan ulaz. Digest jednostavno ne odgovara, i neuspjeh ispliva kao neuspjeh gradnje lanca umjesto kao neuspjeh sadržaja, što vas pošalje pregledavati certifikate i trust storeove dok je stvarni problem pozicija u streamu. Ponovno izgradite memory BIO s BIO_new_mem_buf za svaki prolaz. Košta jednu alokaciju i uklanja mogućnost u potpunosti
Što no-verify zastavica potiskuje a što ne
CMS_NO_SIGNER_CERT_VERIFY potiskuje evaluaciju lanca, a ne traženje certifikata potpisnika. Iznutra OpenSSL razrješuje i prikopčava certifikate potpisnika prije nego konzultira zastavicu, pa nakon prvog prolaza koji nosi tu zastavicu potpisnik je već dostupan i njegovi identifikatori algoritma mogu se čitati odmah. Nema potrebe trčati drugu punu verifikaciju samo da dobijete certifikat potpisnika, a to je ono što ime zastavice mami da pretpostavite
Jedno pravilo vlasništva ide uz to. Referenca potpisnika pripada CMS strukturi i ne smije se oslobođati neovisno. Važeća je dok je struktura, i oslobađanje je proizvodi korupciju čiji se simptom pojavi negdje sasvim drugdje, obično tijekom čišćenja nepovezanog objekta
Zašto uključivanje CRL provjere odbija svaki potpis?
Jer OpenSSL provjerava CRL-ove samo protiv onoga što store već drži i ništa ne dohvaća sam. Ne slijedi CRL distribution pointove i ne govori OCSP. Postavite X509_V_FLAG_CRL_CHECK na store bez ijednog CRL-a i svaki lanac pade s nemogućnošću dobaviti certifikat CRL. Rezultat izgleda kao provjera opoziva koja radi i nalazi probleme. To je provjera opoziva koja nikad nije ni trčala
Backend zato postavlja zastavicu samo kad ConfigureSslCrls doista isporuči barem jedan CRL. Bez ijednog, RevocationStatus vraća se kao pcvsUnsupported, što je pošteno kazivo da pitanje nije odgovoreno. Iz istog razloga OnlineRetrieval nema učinka na ovaj backend i nijedan pcvstOnlineRetrieval checkpoint se ne emitira: nema dohvatnog puta iz kojega bi se javljao napredak
Ovo je dizajnerska pozicija koju vrijedi braniti općenito. Validator koji ne može provjeriti opoziv treba to reći. Javiti neprovjeren certifikat kao neopozvan je pojedinačno najčešći način na koji alati za validaciju potpisa zavode svoje korisnike, i to je točno klasa zabune istražena u zašto validatori odbijaju PAdES potpise
// Checkpointovi puste UI da prikaže koji stupanj trči, i kažu vam koje
// stupnjeve backend stvarno izvodi
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; smiju se razilaziti
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 na biblioteku čiju verziju ne kontrolirate
OpenSSL je preimenovao svoje stack accessore između 1.0 i 1.1, pa ista logička funkcija ima dva moguća imena izvoza ovisno o buildu koji domaćin slučajno ima. Vezivanje prvo razrješuje novije ime i pada natrag na starije, i zabilježi nedostajući simbol tek kad se ni jedno ne razriješi. To je pravi oblik za bilo koje dinamičko vezivanje na biblioteku koju ne isporučujete: preferirajte trenutna imena, trpite povijesna, i javljajte samo pravi nedostatak
SslMissingSymbols je ono što neuspjelo učitavanje pretvara u dijagnostificirajući događaj. Neprazan rezultat na domaćinu koji očito ima libcrypto instaliran znači da je instalirana verzija starija od API-ja kojega ovaj build cilja, što je posve drugačiji support razgovor od biblioteke koja nedostaje. ConfigureSslLibraryPath pokriva drugi uobičajeni slučaj, domaćin s više OpenSSL buildova gdje onaj na defaultnoj search putanji nije onaj koji želite
Birati backend po platformi
Praktičan aranžman je birati pri startanju i zabilježiti koji je odgovorio. Na Windowsima se platformski backend integrira s certificate storeovima kojima poduzeće već upravlja, što je obično ono što želite. Na macOS-u se Keychain backend uklapa u isto razmišljanje i opisan je u verificiranju potpisa sa SecTrustom na macOS-u. OpenSSL je prenosiva opcija, i to je također pravi izbor kad trebate politiku validacije koja je identična preko platformi umjesto one koja slijedi trust store svake platforme
Koji god instalirate, logirajte PadesCmsVerificationBackendName pokraj svake presude koju zabilježite. Pohranjen rezultat validacije bez backenda koji ga je proizveo ne može se kasnije reproducirati, jer tri statusne vrijednosti znače suptilno različite stvari ovisno o tome koji je stack odgovorio. Sloj pregleda potpisa iznad svega ovoga, uključujući kako se PAdES razine javljaju, pokriven je u pregledu PDF digitalnih potpisa i PAdES razina
Sve se to isporučuje kao source uz PDFium Delphi komponentu, što je tu važnije nego obično: za validator potpisa, moći pročitati točno koje zastavice backend postavlja i koje provjere preskače nije ukras, to je jedini način da znate što zelena kvačica u vašoj aplikaciji stvarno tvrdi