Tekninen artikkeli

PDF-allekirjoitusten varmennus OpenSSL:llä PDFium VCL:ssä

PDFium VCL kohtelee CMS-varmennusta vaihdettavana taustajärjestelmänä IPdfCmsVerifier-rajapinnan takana, joten PAdES-validaattori voi ajaa Windowsilla CryptoAPI:n kautta, macOS:lla Keychainin kautta ja missä tahansa, missä OpenSSL on läsnä, ConfigureSslCmsVerifierin kautta. Rajapinta on pieni. Kolme OpenSSL-käytöstä sen alla tuottaa itsevarmoja, vääriä vastauksia, jos toteutat sen naiivisti

Motivaatio on riittävän selvä heti, kun Delphi-sovellus lähtee Windowsista. Allekirjoituksen validointi on yksi niistä harvoista alueista, joissa alustan crypto-pino ei ole toteutuksen yksityiskohta: se päättää, mitkä varmenteet ovat luotettuja, mitkä algoritmit ovat olemassa ja mitä peruutus tarkoittaa. Kovakoodaa yksi, eikä koodi porttaudu. Abstraktoi se huonosti, ja jokainen alusta raportoi erimuotoisen vastauksen, jota kutsuja ei voi vertailla

Mitä abstraktion oikeasti on kannettava

Kaksi varmennuksen muotoa ja kolme itsenäistä tuomiota. PDF-allekirjoitus on irrotettu (detached): allekirjoitettu sisältö on kaksi tavualuetta /Contents-aukon kummallakin puolella, joten VerifyDetached ottaa kaksi segmenttiä yhden puskurin sijaan. Aikaleimatunnus on kiinnitetty (attached), kantaen omaa sisältöään, joten VerifyAttached ottaa vain DERin

Tulos jakautuu kolmeen tilaan, koska ne vastaavat kolmeen eri kysymykseen ja voivat eriä. SignatureStatus sanoo, allekirjoittivatko tavut allekirjoittajan varmenteen avain. TrustStatus sanoo, ketjuuntuuko kyseinen varmenne johonkin, johon luotat. RevocationStatus sanoo, oliko varmenne yhä voimassa relevanttina hetkenä. Dokumentti, jossa on matemaattisesti täydellinen allekirjoitus varmenteesta, josta et ole koskaan kuullut, on voimassa, epäluotettava ja tuntematon, ja sen kutistaminen yhdeksi booleaniksi on tapa, jolla validaattorit päätyvät valehtelemaan käyttäjille

uses
  FPdfCrypto, FPdfCryptoSsl;

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

  ConfigureSslTrustAnchors(LoadCorporateRoots);   // DER, voi olla tyhjä
  ConfigureSslCrls(LoadFreshCrls);                // DER, voi olla tyhjä
  ConfigureSslCmsVerifier;                        // asentaa taustan

  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 näyttää kuriositeetiltä eikä ole. Jokainen OpenSSL-virhekoodi ja jokainen store-lippu ylittää rajan C:n unsigned longina, joka on neljä tavua Windowsilla ja kahdeksan Linuxilla ja macOS:lla. Määrittele se kiinteänä 32-bittisenä tyyppinä, ja koodi toimii Windowsilla ja lukee sitten hiljaa puolet arvosta LP64:llä. Oletettujen leveyksien raportoiminen merkkijonona, johon voi väittää testissä, muuntaa koko alusta-ABI-ajautumisen luokan yhden rivin tarkistukseksi. Jokainen, joka on pohtinut samaa ongelmaa CK_ULONGin kanssa PKCS#11-sidonnassa, tunnistaa sen heti; tuo tarina on artikkelissa PKCS#11-rakenteiden pakkaus ja CK_ULONGin leveys

Miksi toinen varmennuskierros näkee tyhjää sisältöä?

Koska CMS_verify lukee irrotetun sisällön BIOn tiedoston loppuun, ja luettua BIOta ei kelata puolestasi. Varmennus kahdella kierroksella on järkevä suunnittelu: ensin pelkkä kryptografinen allekirjoitus ketjuarvioinnin ollessa tukahdutettuna, sitten täysi arviointi, ja se epäonnistuu epätavallisen harhaanjohtavalla tavalla, jos molemmat kierrokset jakavat yhden BIOn

Toinen kierros saa nolla tavua sisältöä. Detached-tilassa se ei ole virhe, koska tyhjä sisältöpuskuri on laillinen syöte. Tiivistelmä ei yksinkertaisesti täsmää, ja vika ilmestyy ketjunrakennusvikana sisältövikana sijaan, mikä lähettää sinut tutkimaan varmenteita ja trust storeja, kun todellinen ongelma on virran sijainti. Rakenna muisti-BIO uudelleen BIO_new_mem_bufilla joka kierrokselle. Se maksaa yhden varauksen ja poistaa mahdollisuuden kokonaan

Mitä no-verify-lippu tukahduttaa ja mitä ei

CMS_NO_SIGNER_CERT_VERIFY tukahduttaa ketjuarvioinnin, ei allekirjoittajan varmenteen hakua. Sisäisesti OpenSSL ratkaisee ja kiinnittää allekirjoittajan varmenteet ennen kuin se kuulee lippua, joten ensimmäisen lipun kantaneen kierroksen jälkeen allekirjoittaja on jo saatavilla ja sen algoritmitunnisteet voi lukea suoraan. Ei ole tarvetta ajaa toista täyttä varmennusta vain saadakseen allekirjoittajan varmenteen, mikä on se, mihin lipun nimi houkuttelee sinua olettamaan

Yksi omistussääntö kuuluu tuon mukana. Allekirjoittajan viittaus kuuluu CMS-rakenteelle, eikä sitä saa vapauttaa itsenäisesti. Se on voimassa niin kauan kuin rakenne, ja sen vapauttaminen tuottaa korruption, jonka oire ilmestyy aivan muualla, yleensä asiaattoman objektin siivouksen aikana

Miksi CRL-tarkistuksen kytkeminen hylkää jokaisen allekirjoituksen?

Koska OpenSSL tarkistaa CRL:t vain sitä vasten, mitä store jo pitää, eikä nouda mitään omin voimin. Se ei seuraa CRL-jakelupisteitä eikä se puhu OCSP:tä. Aseta X509_V_FLAG_CRL_CHECK storeen, jossa ei ole CRL:ejä, ja jokainen ketju kaatuu kyvyttömyyteen saada varmenteen CRL:tä. Tulos näyttää peruutustarkistukselta, joka toimii ja löytää ongelmia. Kyseessä on peruutustarkistus, joka ei koskaan aja lainkaan

Tausta asettaa siksi lipun vain, kun ConfigureSslCrls on oikeasti toimittanut vähintään yhden CRL:n. Ilman yhtään RevocationStatus palaa pcvsUnsupportedina, mikä on rehellinen lausunto siitä, että kysymykseen ei vastattu. Samasta syystä OnlineRetrievalilla ei ole vaikutusta tähän taustaan eikä pcvstOnlineRetrieval-tarkistuspistettä emitoida: ei ole noutopolkua, josta raportoida edistymistä

PDFium VCL:n OpenSSL CMS -varmentajan kaavio kolmesta ansasta: jaettu sisältö-BIO, joka luetaan tiedoston loppuun, jättää toiselle varmennuskierrokselle nolla tavua, CMS_NO_SIGNER_CERT_VERIFY tukahduttaa ketjuarvioinnin muttei allekirjoittajan hakua, ja CRL-tarkistus tyhjässä storessa hylkää jokaisen ketjun ilman että peruutus koskaan ajaa
Jokainen ansa tuottaa itsevarman väärän tuomion: virran sijainti naamioituu luottamusviaksi, no-verify-lippu tukahduttaa vähemmän kuin sen nimi antaa ymmärtää, ja koskaan ajamaton peruutus näyttää peruutukselta, joka löysi ongelmia

Tämä on suunnittelun asema, joka kannattaa puolustaa yleisesti. Validaattori, joka ei voi tarkistaa peruutusta, pitäisi sanoa sen. Tarkistamattoman varmenteen raportoiminen ei-peruutetuksi on yleisin yksittäinen tapa, jolla allekirjoituksen validointityökalut harhaanjohtavat käyttäjiään, ja kyseessä on täsmälleen se sekaannusten luokka, jota artikkeli miksi validaattorit hylkäävät PAdES-allekirjoitukset tutkii

// Tarkistuspisteet antavat käyttöliittymän näyttää, mikä vaihe on käynnissä,
// ja kertovat, mitkä vaiheet tausta oikeasti suorittaa
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;

// Lue kolme tuomiota erikseen; niiden sallitaan eriävän
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');

Sitoutuminen kirjastoon, jota et voi pinnata

OpenSSL nimesi pinon hakufunktionsa uudelleen 1.0:n ja 1.1:n välillä, joten samalla loogisella funktiolla on kaksi mahdollista vientinimeä riippuen siitä, kumman käännöksen isäntä sattuu omistamaan. Sidonta ratkaisee uudemman nimen ensin ja palaa vanhempaan, ja kirjaa puuttuvan symbolin vasta, kun kumpikaan ei ratkea. Tuo on oikea muoto mille tahansa dynaamiselle sidonnalle kirjastoon, jota et toimita: suosi nykyisiä nimiä, siedä historiallisia ja raportoi vain aito puute

SslMissingSymbols on se, mikä muuntaa epäonnistuneen latauksen diagnosoitavaksi tapahtumaksi. Ei-tyhjä tulos isännällä, jolla on selvästi libcrypto asennettuna, tarkoittaa, että asennettu versio on vanhempi kuin API, jota tämä käännös kohdentaa, mikä on täysin eri tukikeskustelu kuin puuttuva kirjasto. ConfigureSslLibraryPath kattaa toisen yleisen tapauksen, isännän, jolla on useita OpenSSL-käännöksiä ja jossa oletushakupolulla oleva ei ole se, jota haluat

Taustan valinta per alusta

Käytännön järjestely on valita käynnistyksessä ja kirjata, kumpi vastasi. Windowsilla alustatausta integroituu varmenestoreihin, joita yritys jo hallinnoi, mikä on yleensä se, mitä haluat. macOS:lla Keychain-tausta sopii samaan päättelyyn ja se on kuvattu artikkelissa allekirjoitusten varmennus SecTrustilla macOS:lla. OpenSSL on siirrettävä vaihtoehto, ja se on myös oikea valinta, kun tarvitset validointipolitiikan, joka on identtinen alustoilla eikä sellainen, joka seuraa kunkin alustan trust storea

PDFium VCL:n kaavio IPdfCmsVerifier-abstraktiosta, joka kantaa VerifyDetachediä Contents-aukon kahden tavualueen yli ja VerifyAttachediä aikaleimatunnuksille, kolme itsenäistä tuomiota SignatureStatus, TrustStatus ja RevocationStatus sekä per alusta valitut taustat käynnistyksessä CryptoAPI:n, SecTrustin tai ConfigureSslCmsVerifierin kautta
Rajapinta kantaa kaksi varmennuksen muotoa ja kolme tuomiota, koska ne vastaavat eri kysymyksiin ja voivat eriä, ja asennettu tausta kirjataan jokaisen tuomion viereen, jotta tallennetut tulokset voidaan toistaa

Kumman tahansa asennat, kirjaa PadesCmsVerificationBackendName jokaisen tallentamasi tuomion viereen. Tallennettu validointitulos ilman taustaa, joka sen tuotti, ei ole myöhemmin toistettavissa, koska kolmella tila-arvolla on hienovaraisesti eri merkitykset riippuen siitä, kumpi pino vastasi. Allekirjoitusten tarkastuskerros kaiken tämän päällä, mukaan lukien se, miten PAdES-tasot raportoidaan, on käsitelty artikkelissa PDF:n digitaalisten allekirjoitusten ja PAdES-tasojen tarkastus

Kaikki tämä toimituu lähteinä PDFium Delphi -komponentin mukana, mikä merkitsee täällä enemmän kuin yleensä: allekirjoituksen validaattorille kyky lukea tarkasti, mitkä liput tausta asettaa ja mitkä tarkistukset se ohittaa, ei ole mukavuuslisä, vaan ainoa tapa tietää, mitä vihreä valintamerkki sovelluksessasi oikeasti väittää