Műszaki cikk

PDF aláírások ellenőrzése OpenSSL-lel a PDFium VCL-ben

A PDFium VCL a CMS hitelesítést cserélhető backendként kezeli az IPdfCmsVerifier interfész mögött, így a PAdES validátor futhat Windowson CryptoAPI-val, macOS-en a Keychainnel, és bárhol, ahol OpenSSL érhető el, a ConfigureSslCmsVerifier révén. Az interfész kicsi. Három OpenSSL viselkedés ez alatt magabiztos, téves válaszokat produkál, ha naivan implementálja

A motiváció egyértelmű, amint egy Delphi alkalmazás elhagyja a Windowst. Az aláírásvalidáció azon kevés területek egyike, ahol a platform kriptográfiai stackje nem implementációs részlet: az dönti el, mely tanúsítványok számítanak megbízhatónak, mely algoritmusok léteznek, és mit jelent a visszavonás. Bedrótozza az egyiket, és a kód nem portolható. Elrontja az absztrakciót, és minden platform más formájú választ ad, amelyeket a hívó nem tud összehasonlítani

Mit kell valójában hordoznia az absztrakciónak

Két hitelesítési forma és három független verdikt. A PDF aláírás detached: az aláírt tartalom a /Contents lyuk két oldalán elhelyezkedő két bájttartomány, tehát a VerifyDetached két szegmenst kap, nem egy puffert. Az időbélyeg token attached, saját tartalommal utazik, tehát a VerifyAttached csak a DER-t kapja

Az eredmény három státuszra bomlik, mert három különböző kérdésre válaszolnak, és ellentmondhatnak egymásnak. A SignatureStatus azt mondja meg, hogy a bájtokat aláírta-e az aláíró tanúsítványban lévő kulcs. A TrustStatus azt, hogy az a tanúsítvány láncol-e olyanhoz, amelyben megbízik. A RevocationStatus azt, hogy a tanúsítvány a releváns időpontban még érvényes volt-e. Egy dokumentum, amelynek aláírása matematikailag tökéletes, de egy olyan tanúsítványtól származik, amelyről sosem hallott, érvényes, megbízhatatlan és ismeretlen egyszerre, és ezt egyetlen booleanba összecsukni az, ahogy a validátorok végül hazudnak a felhasználóknak

uses
  FPdfCrypto, FPdfCryptoSsl;

var
  Options: TPdfCmsVerifyOptions;
begin
  if not SslAvailable then
    raise Exception.Create('libcrypto not usable: ' + SslMissingSymbols);

  ConfigureSslTrustAnchors(LoadCorporateRoots);   // DER, lehet üres
  ConfigureSslCrls(LoadFreshCrls);                // DER, lehet üres
  ConfigureSslCmsVerifier;                        // telepíti a backendet

  Writeln('backend  : ', PadesCmsVerificationBackendName);
  Writeln('library  : ', SslLibraryPath, ' ', SslLibraryVersion);
  Writeln('ABI      : ', SslAbiLayout);           // ulong=<n> long=<n>

  Options := TPdfCmsVerifyOptions.Default;
  Options.CheckRevocation := True;
  Options.CollectChainCertificates := True;
end;

A SslAbiLayout kuriozitásnak tűnik, és nem az. Minden OpenSSL hibakód és minden tárflag C unsigned long-ként kel át a határon, amely négy bájt Windowson és nyolc Linuxon és macOS-en. Deklarálja fix 32 bites típusként, és a kód Windowson működik, majd LP64-en csendben egy érték felét olvassa. A feltételezett szélességek jelentése olyan sztringként, amelyet egy tesztben assertelni tud, egy egész osztálynyi platform ABI csúszást fordít egysoros ellenőrzéssé. Aki ugyanezzel a problémával küzdött meg a CK_ULONG kapcsán egy PKCS#11 kötésben, azonnal ráismer; az a történet a PKCS#11 struktúracsomagolás és a CK_ULONG szélessége cikkben olvasható

Miért üres tartalmat lát a második hitelesítési menet?

Mert a CMS_verify a detached tartalom BIO-ját fájl végéig olvassa, és egy egyszer kiolvasott BIO-t nem teker vissza Ön helyett. Két menetes hitelesítés ésszerű tervezés: először csak a kriptográfiai aláírás, a láncértékelés elnyomásával, aztán a teljes értékelés, és kivételesen megtévesztő módon bukik meg, ha mindkét menet ugyanazt a BIO-t használja

A második menet nulla bájtnyi tartalmat kap. Detached módban ez nem hiba, mert az üres tartalompuffer legális bemenet. A kivonat egyszerűen nem egyezik, és a hiba láncépítési hibaként tör felszínre, nem tartalmi hibaként, ami tanúsítványok és tanúsítványtárolók átvizsgálására küldi, miközben a valódi probléma egy streampozíció. Építse újjá minden menethez a memória BIO-t a BIO_new_mem_buf hívásával. Egy allokációba kerül, és a lehetőség teljes egészében megszűnik

Mit nyom el a no-verify flag, és mit nem

A CMS_NO_SIGNER_CERT_VERIFY a láncértékelést nyomja el, nem az aláíró tanúsítvány felkutatását. Belül az OpenSSL feloldja és csatolja az aláíró tanúsítványokat, mielőtt egyáltalán megnézné a flag-et, tehát az olyan első menet után, amely belevette ezt a flag-et, az aláíró már rendelkezésre áll, és az algoritmusazonosítói azonnal kiolvashatók. Nincs szükség második teljes hitelesítés futtatására pusztán az aláíró tanúsítvány megszerzéséért, mégis pontosan erre csábít a flag neve

Ehhez egy tulajdonosi szabály tartozik. Az aláíróhivatkozás a CMS struktúrához tartozik, és nem szabad önállóan felszabadítani. Addig érvényes, ameddig a struktúra, és a felszabadítása olyan sérülést produkál, amelynek tünete egészen máshol mutatkozik, általában egy közömbös objektum takarítása közben

Miért utasít el minden aláírást a CRL-ellenőrzés bekapcsolása?

Mert az OpenSSL a CRL-eket csak ahhoz képest ellenőrzi, amit a tár már tart, és semmit nem tölt le saját maga. Nem követi a CRL disztribúciós pontokat, és OCSP-t nem ismer. X509_V_FLAG_CRL_CHECK beállítása egy olyan táron, amelyben nincs CRL, minden láncot elbuktat tanúsítvány-CRL beszerzésének képtelenségével. Az eredmény úgy néz ki, mint egy visszavonás-ellenőrzés, amely működik és problémákat talál. Az valójában egy sosem lefutott visszavonás-ellenőrzés

A backend ezért csak akkor állítja be a flag-et, ha a ConfigureSslCrls ténylegesen szállított legalább egy CRL-t. Enélkül a RevocationStatus pcvsUnsupported-ként tér vissza, ami őszinte kijelentés arról, hogy a kérdés meg nem válaszolt maradt. Ugyanebből az okból az OnlineRetrieval-nak nincs hatása erre a backendre, és nem érkezik pcvstOnlineRetrieval checkpoint: nincs letöltési útvonal, amelyről a haladást jelenteni lehetne

PDFium VCL OpenSSL CMS hitelesítő ábrája a három csapdáról: a fájl végéig olvasott közös tartalom BIO nulla bájtot hagy a második hitelesítési menetnek, a CMS_NO_SIGNER_CERT_VERIFY a láncértékelést nyomja el, de az aláíró felkutatását nem, a CRL-ellenőrzés pedig egy üres táron minden láncot elutasít, miközben a visszavonás-ellenőrzés egyszer sem futott le
Minden csapda magabiztos, téves verdiktet ad: egy streampozíció adja ki magát megbízhatósági hibának, a no-verify flag kevesebbet nyom el, mint amire neve utal, és a le nem futott visszavonás-ellenőrzés úgy tűnik, mint amely problémákat talált

Ez általánosságban is olyan tervezési álláspont, amelyet érdemes megvédeni. Egy validátor, amely nem tudja ellenőrizni a visszavonást, mondja is ki. Egy ellenőrizetlen tanúsítvány nem visszavontként jelentése a leggyakoribb módja annak, hogy az aláírásvalidáló eszközök félrevezetik a felhasználóikat, és pontosan az a zavarosztály, amelyet a miért utasítanak el a validátorok PAdES aláírásokat cikk jár körül

// A checkpointok megmutatják a felületnek, éppen mely szakasz fut, és
// megmondják, mely szakaszokat hajt végre valójában egy backend
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;

// Olvassa a három verdiktet külön-külön; szabad, hogy ellentmondjanak
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');

Kötés olyan könyvtárhoz, amelyet nem rögzíthet

Az OpenSSL az 1.0 és az 1.1 között átnevezte a stack-elérés függvényeit, tehát ugyanannak a logikai függvénynek két lehetséges exportneve van aszerint, hogy a gép éppen mely buildtel rendelkezik. A kötés előbb az újabb nevet oldja fel, és a régebbire esik vissza, és csak akkor jegyez fel hiányzó szimbólumot, ha egyik sem oldódik fel. Ez a helyes forma bármilyen dinamikus kötéshez olyan könyvtárhoz, amelyet Ön nem szállít: az aktuális neveket részesítse előnyben, tűrje a történelmieket, és csak a valódi hiányzást jelentse

A SslMissingSymbols az, ami egy megbukott betöltésből diagnosztizálható eseményt farag. Nem üres eredmény egy olyan gépen, amely nyilvánvalóan telepített libcrypto-val rendelkezik azt jelenti, hogy a telepített verzió régebbi, mint az az API, amelyet ez a build célz, ami teljesen más támogatási beszélgetés, mint egy hiányzó könyvtár. A ConfigureSslLibraryPath a másik gyakori esetet fedi le, a több OpenSSL buildtel rendelkező gépet, ahol az alapértelmezett keresési úton lévő nem az, amelyet Ön akar

Backend kiválasztása platformonként

A gyakorlati berendezkedés az, hogy induláskor választ, és rögzíti, melyik felelt. Windowson a platform backend integrálódik azokkal a tanúsítványtárolókkal, amelyeket a vállalat már kezel, ami normálisan az, amit Ön akar. macOS-en a Keychain backend ugyanarra az érvelésre illeszkedik, és a aláírások ellenőrzése SecTrust-tal macOS-en cikk írja le. Az OpenSSL a hordozható opció, és akkor is a helyes választás, amikor platformokon át azonos validációs politikára van szüksége, nem olyanra, amely minden platform tanúsítványtárolóját követi

PDFium VCL ábra az IPdfCmsVerifier absztrakcióról, amely a VerifyDetached-et a Contents lyuk két oldalán lévő két bájttartományra, a VerifyAttached-ot az időbélyeg tokenekre viszi, továbbá a három független verdiktről, a SignatureStatus-ról, TrustStatus-ról és RevocationStatus-ról, valamint a platformonként induláskor CryptoAPI, SecTrust vagy ConfigureSslCmsVerifier útján kiválasztott backendekről
Az interfész két hitelesítési formát és három verdiktet hordoz, mert különböző kérdésekre válaszolnak, és ellentmondhatnak egymásnak, a telepített backend pedig minden verdikt mellé rögzítésre kerül, hogy a tárolt eredmények reprodukálhatók legyenek

Akármelyiket is telepíti, naplózza a PadesCmsVerificationBackendName-et minden rögzített verdikt mellé. Egy tárolt validációs eredmény a legyártó backend nélkül később nem reprodukálható, mert a három státuszérték finoman mást jelent aszerint, hogy melyik stack felelt. Mindezek fölötti aláírásvizsgálati réteget, beleértve a PAdES szintek jelentési módját is, a PDF digitális aláírások és PAdES szintek vizsgálata cikk tárgyalja

Mindez forrásként szállít a PDFium Delphi komponens részeként, ami itt a szokásosnál is többet számít: egy aláírásvalidátor számára az, hogy pontosan kiolvashassa, mely flag-eket állít be egy backend, és mely ellenőrzéseket hagyja ki, nem kellemes extra, hanem az egyetlen módja annak, hogy tudja, mit állít valójában a zöld pipa az Ön alkalmazásában