PDFium VCL obravnava preverjanje CMS kot zamenljivo zaledje za vmesnikom IPdfCmsVerifier, tako da lahko validiator PAdES teče na Windows skozi CryptoAPI, na macOS skozi verigo ključev in kjerkoli je prisoten OpenSSL skozi ConfigureSslCmsVerifier. Vmesnik je majhen. Trije vedenji OpenSSL pod njim proizvedejo samozavestne, napačne odgovore, če ga implementirate naivno
Motivacija je dovolj jasna, takoj ko Delphi aplikacija zapusti Windows. Validacija podpisov je eno od peščice področij, kjer kriptografski sklad platforme ni implementacijska podrobnost: odloča, kateri certifikati so zaupanja vredni, kateri algoritmi obstajajo in kaj pomeni razveljavitev. Trdo kodirati enega in koda se ne preseli. Abstrahirati slabo in vsaka platforma sporoči drugače oblikovan odgovor, ki ga klicatelj ne more primerjati
Kaj mora abstrakcija dejansko nositi
Dve obliki preverjanja in tri neodvisne sodbe. Podpis PDF je ločen (detached): podpisana vsebina sta dva bajtna obsega na obeh straneh luknje /Contents, zato VerifyDetached vzame dva odseka in ne enega medpomnilnika. Žeton časovnega žiga je pripet, nosi svojo lastno vsebino, zato VerifyAttached vzame le DER
Rezultat se razcepi na tri statusa, ker odgovarjajo na tri različna vprašanja in se lahko ne strinjajo. SignatureStatus pravi, ali so bajte podpisale s ključ v certifikatu podpisovalca. TrustStatus pravi, ali ta certifikat veriži v nekaj, čemu zaupate. RevocationStatus pravi, ali je bil certifikat še veljaven v relevantnem času. Dokument z matematično popolnim podpisom iz certifikata, o katerem ste nikoli slišali, je veljaven, nezaupanja vreden in neznan — strjanje tega v en sam boolean pa je način, na katerega validiatorji končajo z laganjem uporabnikov
uses
FPdfCrypto, FPdfCryptoSsl;
var
Options: TPdfCmsVerifyOptions;
begin
if not SslAvailable then
raise Exception.Create('libcrypto not usable: ' + SslMissingSymbols);
ConfigureSslTrustAnchors(LoadCorporateRoots); // DER, je lahko prazen
ConfigureSslCrls(LoadFreshCrls); // DER, je lahko prazen
ConfigureSslCmsVerifier; // namesti zaledje
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 kot radovednost in ni. Vsaka koda napake OpenSSL in vsaka zastavica shrambe prečkata mejo kot C unsigned long, ki je štiri bajte na Windows in osem na Linux in macOS. Razglasiti ga kot fiksni 32-bitni tip in koda deluje na Windows, nato pa tiho prebere pol vrednosti na LP64. Sporočanje privzetih širin kot niz, na katerem lahko v testu postavite trditev, spremeni celoten razred drsenja platformskih ABI v enovrstično preverjanje. Vsak, ki je predelal isto težavo s CK_ULONG v vezavi PKCS#11, jo bo prepoznal takoj; ta zgodba je v pakiranju struktur PKCS#11 in širini CK_ULONG
Zakaj drugi prehod preverjanja vidi prazno vsebino?
Ker CMS_verify bere ločeni vsebinski BIO do konca datoteke, BIO, ki je bil bran, pa vam ni prevrtan nazaj. Preverjanje v dveh prehodih je razumen načrt — najprej samo kriptografski podpis z zatrto evalvacijo verige, nato polna evalvacija — in spodleti na nenavadno zavarljiv način, če oba prehoda delita en BIO
Drugi prehod dobi nič bajtov vsebine. V ločenem načinu to ni napaka, ker je prazen medpomnilnik vsebine zakonit vhod. Povzetek se preprosto ne ujema, spodletel pa pride na dan kot spodletel gradnje verige in ne kot spodletel vsebine, kar vas pošlje pregledovat certifikate in shrambe zaupanja, medtem ko je dejanska težava položaj v toku. Znova zgradite pomnilniški BIO z BIO_new_mem_buf za vsak prehod. Stane eno dodelitev in odstrani možnost v celoti
Kaj zastavica no-verify zatre in česa ne
CMS_NO_SIGNER_CERT_VERIFY zatre evalvacijo verige, ne iskanja certifikata podpisovalca. Notranje OpenSSL razreši in pripne certifikate podpisovalcev, preden se posvetuje z zastavico, tako da po prvem prehodu, ki nosi to zastavico, je podpisovalec že na voljo in njegovi identifikatorji algoritmov se lahko preberejo takoj. Ni treba zagnati drugega polnega preverjanja, samo da bi pridobili certifikat podpisovalca — to pa je tisto, kar vas ime zastavice mami verjeti
Eno pravilo lastništva gre s tem. Sklic podpisovalca pripada strukturi CMS in se ga ne sme sprostiti neodvisno. Veljaven je, dokler je struktura, njegova sprostitev pa proizvede pokvarjenost, katere simptom se pokaže povsem nekje drugje — običajno med čiščenjem nepovezanega objekta
Zakaj vklop preverjanja CRL zavrne vsak podpis?
Ker OpenSSL preverja CRL-e le proti tistemu, kar shramba že drži, in sam ne pridobi nič. Ne sledi točkam distribucije CRL in ne govori OCSP. Nastavite X509_V_FLAG_CRL_CHECK na shrambi brez CRL-jev in vsaka veriga spodleti z nesposobnostjo pridobiti CRL certifikata. Rezultat izgleda kot delujoče preverjanje razveljavitev, ki najde težave. To je preverjanje razveljavitev, ki sploh ni nikoli teklo
Zaledje zato nastavi zastavico le, ko je ConfigureSslCrls dejansko preskrbela vsaj en CRL. Brez njega se RevocationStatus vrne kot pcvsUnsupported, kar je iskrena izjava, da na vprašanje ni bilo odgovorjeno. Iz istega razloga OnlineRetrieval na tem zaledju nima učinka in nič kontrolna točka pcvstOnlineRetrieval ni oddana: ni poti pridobivanja, iz katere bi se sporočal napredek
To je oblikovni položaj, ki ga vredi na splošno braniti. Validator, ki ne more preveriti razveljavitev, naj to pove. Sporočanje nepreverjenega certifikata kot nerazveljavljenega je najpogostejši način, kako orodja za validacijo podpisov zavedejo svoje uporabnike, in je natanko tisti razred zmede, ki ga raziskuje zakaj validatorji zavračajo podpise PAdES
// Kontrolne točke pustijo UI pokazati, katera stopnja teče, in povedo vam,
// katere stopnje zaledje dejansko izvaja
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;
// Berite tri sodbe ločeno; smejo se ne strinjati
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');
Vezanje na knjižnico, ki je ne morete pripeti
OpenSSL je med 1.0 in 1.1 preimenoval svoje dostopovalce sklada, tako da ima ista logična funkcija dve možni izvoženi imeni, odvisno od gradnje, ki jo gostitelj slučajno ima. Vezanje najprej razreši novejše ime in profilira na starejše, zabeleži manjkajoči simbol pa šele, ko se ne razreši nobeno. To je prava oblika za vsako dinamično vezanje proti knjižnici, ki je ne pošiljate: raje trenutna imena, prenašajte zgodovinska in sporočajte le pravo odsotnost
SslMissingSymbols je tisto, kar spodletel nalaganje spremeni v diagnosticirljiv dogodek. Neprazen rezultat na gostitelju, ki očitno ima nameščen libcrypto, pomeni, da je nameščena verzija starejša od API-ja, ki ga ta gradnja cilja — to je popolnoma drugačen pogovor o podpori kot manjkajoča knjižnica. ConfigureSslLibraryPath pokriva drug pogost primer: gostitelj z več gradnjami OpenSSL, kjer tista na privzeti iskalni poti ni tista, ki jo želite
Izbira zaledja na platformo
Praktična ureditev je izbrati ob zagonu in zabeležiti, katero je odgovorilo. Na Windows se platformsko zaledje vključi s shrambami certifikatov, ki jih podjetje že upravlja — običajno to je tisto, kar želite. Na macOS zaledje verige ključev ustreza istemu sklepanju in je opisano v preverjanju podpisov s SecTrust na macOS. OpenSSL je prenosljiva možnost in je tudi prava izbira, kadar potrebujete politiko validacije, identično čez platforme, in ne takšno, ki sledi shrambi zaupanja vsake platforme
Katero koli namestite, beležite PadesCmsVerificationBackendName ob vsaki sodbi, ki jo zabeležite. Shranjen rezultat validacije brez zaledja, ki ga je proizvedel, se kasneje ne more reproducirati, ker tri vrednosti statusov pomenijo subtilno različne stvari, odvisno od tega, kateri sklad je odgovoril. Plast pregledovanja podpisov na vrhu vsega tega, vključno s tem, kako se sporočajo ravni PAdES, je pokrita v pregledovanju digitalnih podpisov PDF in ravni PAdES
Vse to pride kot vir z PDFium Delphi component, kar je tu pomembneje kot običajno: za validator podpisov je moč prebrati točno, katere zastavice zaledje nastavi in katere preverjanja preskoči, ni prijeten dodatek — to je edini način, da veste, kaj dejansko trdi zeleno kljukica v vaši aplikaciji