PDFium VCL elgiasi su CMS tikrinimu kaip su keičiama posisteme už IPdfCmsVerifier sąsajos, tad PAdES validatorius gali bėgti ant Windows per CryptoAPI, ant macOS per Keychain, o ten, kur tik yra OpenSSL, per ConfigureSslCmsVerifier. Sąsaja maža. Trys OpenSSL elgsenos po ja duoda pasitikinčius, bet neteisingus atsakymus, jei ją realizuojate naiviai
Motyvacija tampa akivaizdi, vos Delphi programa palieka Windows. Parašų validacija yra viena iš nedaugelio sričių, kur platformos crypto krūva nėra realizacijos detalė: ji nusprendžia, kurie sertifikatai pasitikėtini, kurie algoritmai egzistuoja ir ką atšaukimas iš viso reiškia. Įrašius vieną kietu kodu — kodas neperkeliamas. Abstrakcija padaryta prastai — kiekviena platforma praneša kitokios formos atsakymą, kurio kvietėjas negali palyginti
Ką abstrakcija iš tikrųjų turi nešti
Dvi tikrinimo formos ir trys nepriklausomos išvados. PDF parašas yra atskirtas: pasirašytas turinys yra du baitų ruožai abipus /Contents skylės, tad VerifyDetached ima du segmentus, o ne vieną buferį. Laiko žymės žetonas yra pritvirtintas ir neša savo turinį, tad VerifyAttached ima tik DER
Rezultatas skyla į tris būsenas, nes jos atsako į tris skirtingus klausimus ir gali nesutapti. SignatureStatus sako, ar baitus pasirašė pasirašytojo sertifikate esantis raktas. TrustStatus sako, ar tas sertifikatas grandine siekia kažką, kuo pasitikite. RevocationStatus sako, ar sertifikatas atitinkamu metu vis dar buvo galiojantis. Dokumentas su matematiškai tobulu parašu iš sertifikato, apie kurį niekada nebūvote girdėję, yra galiojantis, nepatikimas ir nežinomas, o sutraukus visa tai į vieną boolean, validatoriai taip ir baigia meluoti vartotojams
uses
FPdfCrypto, FPdfCryptoSsl;
var
Options: TPdfCmsVerifyOptions;
begin
if not SslAvailable then
raise Exception.Create('libcrypto not usable: ' + SslMissingSymbols);
ConfigureSslTrustAnchors(LoadCorporateRoots); // DER, gali būti tuščia
ConfigureSslCrls(LoadFreshCrls); // DER, gali būti tuščia
ConfigureSslCmsVerifier; // instaliuoja posistemę
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 atrodo kaip įdomybė, bet ja nėra. Kiekvienas OpenSSL klaidos kodas ir kiekviena saugyklos vėliava kerta ribą kaip C unsigned long, kuris ant Windows yra keturi baitai, o ant Linux ir macOS — aštuoni. Deklaravus jį kaip fiksuotą 32 bitų tipą, kodas veikia ant Windows, o paskui tyliai skaito pusę reikšmės ant LP64. Prielaidų pločių pranešimas eilute, kurią gali patikrinti teste, visą platformų ABI dreifo klasę paverčia vienos eilutės patikra. Kas bedirbo su ta pačia problema per CK_ULONG PKCS#11 susiejime, tą atpažins iš karto; ta istorija yra straipsnyje PKCS#11 struktūrų pakavimas ir CK_ULONG plotis
Kodėl antras tikrinimo praėjimas mato tuščią turinį?
Nes CMS_verify skaito atskirtą turinio BIO iki failo galo, o nuskaityta BIO savaime į pradžią nesugrąžinama. Tikrinimas dviem praėjimais yra pagrįstas dizainas: pirmas praėjimas tik kriptografiniam parašui, slopinant grandinės vertinimą, tada pilnas vertinimas, ir jis krinta neįprastai apgaulingai, jei abu praėjimai dalijasi vienu BIO
Antras praėjimas gauna nulį baitų turinio. Atskirtame režime tai nėra klaida, nes tuščias turinio buferis yra legali įvestis. Maiša tiesiog nesutampa, o nesėkmė pasirodo kaip grandinės kūrimo nesėkmė, o ne turinio nesėkmė, kas nusiunčia jus tikrinti sertifikatus ir pasitikėjimo saugyklas, kol tikroji problema yra srauto pozicija. Perkurkite atminties BIO su BIO_new_mem_buf kiekvienam praėjimui. Tai kainuoja vieną alokavimą ir pašalina tą galimybę visai
Ką slopina ir ko neslopina no-verify vėliava
CMS_NO_SIGNER_CERT_VERIFY slopina grandinės vertinimą, o ne pasirašytojo sertifikato paiešką. Viduje OpenSSL išsprendžia ir pritvirtina pasirašytojo sertifikatus dar prieš tai, kai pasiekia vėliavą, tad po pirmo praėjimo su ta vėliava pasirašytojas jau prieinamas, ir jo algoritmo identifikatorius galima skaityti iškart. Nereikia leisti antro pilno tikrinimo vien tam, kad gautumėte pasirašytojo sertifikatą — būtent to vilioja manyti vėliavos pavadinimas
Su tuo einą viena nuosavybės taisyklė. Pasirašytojo nuoroda priklauso CMS struktūrai, ir jos negalima atlaisvinti atskirai. Ji galioja, kol galioja struktūra, o jos atlaisvinimas duoda atminties sugadinimą, kurio simptomas pasirodo visiškai kitoje vietoje, dažniausiai per visai nesusijusio objekto išvalymą
Kodėl įjungus CRL tikrinimą atmetami visi parašai?
Nes OpenSSL tikrina CRL tik pagal tai, ką saugykla jau turi, ir pats nieko neparsiunčia. Jis neseka CRL distribution points ir nekalba OCSP. Nustatę X509_V_FLAG_CRL_CHECK saugykloje be jokių CRL, gausite, kad kiekviena grandinė krinta su nesugebėjimu gauti sertifikato CRL. Rezultatas atrodo kaip atšaukimo tikrinimas, veikiantis ir randantis problemų. Iš tikrųjų tai atšaukimo tikrinimas, kuris nesuveikia nė karto
Posistemė todėl nustato vėliavą tik tada, kai ConfigureSslCrls tikrai padavė bent vieną CRL. Be jos RevocationStatus grįžta kaip pcvsUnsupported, kas yra nuoširdus pareiškimas, jog klausimas liko be atsakymo. Iš tos pačios priežasties OnlineRetrieval neturi jokio poveikio šiai posistemėi ir joks pcvstOnlineRetrieval kontrolinis taškas nevyksta: nėra parsiuntimo kelio, kuriuo būtų galima pranešti pažangą
Tai projektavimo pozicija, verta ginti apskritai. Validatorius, negalintis tikrinti atšaukimo, turėtų tai pasakyti. Netikrinto sertifikato pateikimas kaip neatšaukto yra pats dažniausias būdas, kuriuo parašų validacijos įrankiai apgaudinėja savo vartotojus, ir tai būtent ta sumaišties klasė, tiriama straipsnyje kodėl validatoriai atmeta PAdES parašus
// Kontroliniai taškai leidžia UI parodyti, kuris etapas bėga, ir pasako,
// kuriuos etapus posistemė iš tikrųjų atlieka
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;
// Skaitykite tris išvadas atskirai; jos turi teisę nesutapti
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');
Susiejimas su biblioteka, kurios negalite užfiksuoti
OpenSSL pervadino savo krūvos prieigos funkcijas tarp 1.0 ir 1.1, tad ta pati loginė funkcija turi du galimus eksportuotus vardus, priklausomai nuo to, kuri versija yra hosto sistemoje. Susiejimas pirmiausia išsprendžia naujesnį vardą, o nepavykus grįžta prie senesnio, ir trūkstamą simbolį užrašo tik tada, kai neveikia nė vienas. Tai teisinga forma bet kokiam dinaminiam susiejimui su biblioteka, kurios jūs negabenate kartu su programa: teikite pirmenybę dabartiniams vardams, toleruokite istorinius ir praneškite tik tikrą nebuvimą
SslMissingSymbols yra tai, kas nepavykusį pakrovimą paverčia diagnozuojamu įvykiu. Netuščias rezultatas hoste, kuriame aiškiai instaliuota libcrypto, reiškia, kad instaliuota versija senesnė už API, į kurią šis susiejimas taikosi, ir tai visiškai kitoks palaikymo pokalbis nei trūkstama biblioteka. ConfigureSslLibraryPath padengia kitą dažną atvejį: hostas su keliomis OpenSSL instaliacijomis, kai ta numatytajame paieškos kelyje nėra ta, kurios norite
Posistemės pasirinkimas kiekvienai platformai
Praktinis išdėstymas yra pasirinkti paleidimo metu ir užrašyti, kuri atsakė. Ant Windows platformos posistemė integruojasi su sertifikatų saugyklomis, kurias įmonė jau valdo, ir tai paprastai yra tai, ko norite. Ant macOS Keychain posistemė remiasi ta pačia logika ir aprašyta straipsnyje parašų tikrinimas su SecTrust ant macOS. OpenSSL yra perkeliamasis variantas, ir jis taip pat teisingas pasirinkimas, kai reikia visose platformose identiškos validacijos politikos, o ne tokios, kuri seka kiekvienos platformos pasitikėjimo saugyklą
Kurios bebūtumėte instaliavę, žurnalinkite PadesCmsVerificationBackendName šalia kiekvienos užrašytos išvados. Saugomas validacijos rezultatas be posistemės, jį pagaminusios, negali būti vėliau atkurtas, nes trys būsenų reikšmės reiškia smulkiai skirtingus dalykus, priklausomai nuo to, kuri krūva atsakė. Parašų apžiūros sluoksnis virš viso to, įskaitant tai, kaip pranešami PAdES lygmenys, aprašytas straipsnyje PDF skaitmeninių parašų ir PAdES lygmenų apžiūra
Visa tai keliauja kaip šaltinis su PDFium Delphi komponentu, ir čia tai svarbiau nei įprastai: parašų validatoriui galimybė perskaityti, kokias vėliavas posistemė tikrai nustato ir kuriuos tikrinimus praleidžia, nėra malonus priedas — tai vienintelis būdas sužinoti, ką žalias varnelės ženklas jūsų programoje iš tikrųjų deklaruoja