Tekninen artikkeli

libcurl-aikaleimatausta PDFium VCL:lle FPC:llä

PDFium VCL lähettää RFC 3161 -aikaleimapyynnöt libcurlin kautta ei-Windows-kohteilla, dynaamisesti sidottuna kahdeksaan symboliin, peilaten Windows-taustajärjestelmän muotoa, joka sitoutuu WinHTTP:hen. Kaksi asetusvalintaa päättää, onko kuljetus luotettava kuormituksessa, ja koko yksikkö varmistettiin koneella, joka ei voinut kääntää sitä kohdealustalleen

Aikaleimaus on se, mikä tekee allekirjoituksesta jotain, mikä selviää varmenteen vanhenemisesta, ja se on verkkotoiminto, joka istuu allekirjoitusoperaation sisällä. Tuo yhdistelmä tekee kuljetusvalinnasta merkityksellisen tavalla, jolla se yleensä ei ole: se ajaa taustasäikeessä, se puhuu palvelimelle, jota et hallitse, ja jumitus siellä pysäyttää allekirjoitusputken, ei sivun latausta

Miksi libcurl eikä FPC:n HTTP-asiakas?

Koska vaihtoehto raahaa TLS-pinon repositorioon ja tekee sinusta sitten sen versiotunnistuksen ylläpitäjän. Ilmeinen reitti Free Pascalilla on fphttpclient OpenSSL-socketkerroksella, ja se kaatuu yksityiskohtiin: FPC 3.2.2:n OpenSSL-sidonnat tunnistavat OpenSSL 3.x:n epäluotettavasti useimmissa nykyisissä jakeluissa, ja macOS lisää LibreSSL-erot päälle. Se, mikä alkaa pienenä HTTP-kutsuna, muuttuu toisen TLS-ABI:n jatkuvaksi ylläpidoksi

libcurl ratkaisee oman TLS-taustansa ja validoi ketjut alustan trust storea vasten, joten Pascal-puoli ei tarvitse niistä mitään. Sidontakerros on kahdeksan symbolia. Tuo määrä on argumentti: pienempi pinta koodisi ja liikkuvan riippuvuuden välillä tarkoittaa vähemmän paikkoja, joissa jakelun päivitys voi rikkoa sinut, ja se vastaa olemassa olevaa Windows-taustaa, joka sitoo kourallisen WinHTTP-sisääntulopisteitä samalla tavalla

uses
  FPdfTsaFpc;

var
  ReqDer, RespDer: TBytes;
begin
  if not TsaHttpAvailable then
    raise Exception.Create('no HTTP transport for timestamping');

  Writeln('TSA transport: ', TsaHttpBackendName);

  ReqDer := BuildTimeStampQuery(DocumentDigest);
  if PostTimeStampQuery('https://tsa.example.org/tsr', ReqDer, RespDer) then
    AttachTimeStampToken(RespDer)
  else
    raise Exception.Create('timestamp request failed');
end;

C-variaadisen funktion määrittely Pascalissa

curl_easy_setopt ja curl_easy_getinfo ovat variaadisia C-puolella, eikä Object Pascal pysty ilmaisemaan sitä. Toimiva lähestymistapa on määritellä useita kiinteitä prototyyppejä, yksi per argumenttiluokka, kaikki osoittaen samaan vientisymboliin: longin ottava muunnelma, osoittimen ottava muunnelma ja niin edelleen, valittuna kutsupaikalla sen mukaan, mitä oikeasti passitat

Tämä on turvallista tarkasta syystä, joka kannattaa ymmärtää kopioimisen sijaan. Kukin niistä argumenttityypeistä passataan kokonaislukurekisterissä voimassa olevan alustan kutsukonvention alla, mikä on täsmälleen se paikka, josta C-toteutuksen va_arg lukee sen. Temppu pitää siis kokonaisluvuille, osoittimille ja kahvoille, ja se ei pidä liukulukuargumenteille, jotka matkustavat eri rekistereissä. Älä lisää doublea ottavaa muunnelmaa olettaen, että kaava yleistyy

// Yksi vientisymboli, useita kiinteitä prototyyppejä. Jokainen muunnelma
// passaa argumenttinsa kokonaislukurekisterissä, ja juuri sieltä C-puoli
// lukee sen. Liukulukumuunnelma ei toimisi, eikä sitä saa lisätä
type
  TCurlSetOptLong = function(Handle: Pointer; Option: Integer;
    Value: NativeInt): Integer; cdecl;
  TCurlSetOptPtr  = function(Handle: Pointer; Option: Integer;
    Value: Pointer): Integer; cdecl;

var
  curl_easy_setopt_long: TCurlSetOptLong;
  curl_easy_setopt_ptr:  TCurlSetOptPtr;

Kaksi asetusta, jotka päättävät, valmistuuko pyyntö

Ensimmäinen on eksplisiittinen tyhjä Expect:-otsikko. libcurl kytkee HTTP 100-continue -kättelyn päälle pyyntörungoille, jotka ylittävät suunnilleen kilotavun, ja sertifikaattipyynnön sisältävä aikaleimakysely ylittää yleensä tuon kynnyksen. Osa TSA-palvelimista ei koskaan vastaa jatkoa, joten asiakas odottaa koko timeoutin ennen rungon lähettämistä, jonka palvelin olisi hyväksynyt heti. Tyhjän Expect:-otsikon lähettäminen tukahduttaa kättelyn, ja pyyntö menee läpi yhdellä kierroksella

Toinen on CURLOPT_NOSIGNAL, joka on asetettava. Ilman sitä libcurl toteuttaa nimentoratkaisun timeoutinsa SIGALRMilla, ja tuo mekanismi ei ole säieturvallinen. Allekirjoitus ajaa taustasäikeessä, joten oletuskäytös on piilevä kaatuma, joka ilmestyy samanaikaisuudessa eikä koskaan yksisäikeisessä testissä. Lipun asettaminen poistaa signaaliin perustuvan polun ja maksaa vain resolverin timeoutin tarkkuuden

Molemmat viat jakavat profiilin, joka tekee niistä kalliita löytää myöhemmin. Kumpikaan ei ilmesty funktionaalisessa testissä hyvin käyttäytyvää palvelinta vastaan yhdellä säikeellä. Molemmat ilmestyvät tuotannossa, yhtä tiettyä TSA:ta vastaan, kuormituksessa. Kun sidot verkkokirjaston, lue, mitä sen oletukset olettavat prosessistasi, ennen kuin oletat niiden täsmäävän

PDFium VCL:n libcurl-aikaleimakuljetuksen kaavio, joka näyttää curl_easy_setoptin määriteltynä kiinteinä long- ja osoitin-Pascal-prototyyppeinä, jotka passavat argumentit kokonaislukurekistereissä, tyhjän Expect-otsikon, joka tukahduttaa HTTP 100-continue -kättelyn, CURLOPT_NOSIGNALin, joka poistaa SIGALRM-polun taustasäikeissä, ja kuljetustason vastausrajan
Kaksi asetusta päättää, valmistuuko pyyntö: tyhjä Expect-otsikko välttää palvelimet, jotka eivät koskaan vastaa jatkoa, ja NOSIGNAL pitää nimentoratkaisun timeoutit poissa signaalipolulta, kun allekirjoitus ajaa taustasäikeessä

Miten varmistat koodin, jota kääntäjäsi ei koskaan näe?

Saamalla kääntäjän näkemään sen joka tapauksessa, hallitun kopion kautta. Kehityskoneella täällä ei ole Linux- eikä macOS-ristikkäiskääntäjää, joten aikaleimausyksikön ei-Windows-haarat eivät koskaan saavuta koodigeneraattoria tavallisessa käännöksessä. Koodi, jota ei koskaan käännetä, on koodia, joka mätänee hiljaa: uudelleennimetty jaettu tyyppi, muuttunut parametrilista, lisätty yksikköriippuvuus, eikä kukaan huomaa kuukausiin

Tekniikka on mekaaninen. Kopioi yksikkö väliaikaiseen hakemistoon, nimeä se uudelleen ja korvaa jokainen Windows-ehto, sekä {$IFDEF MSWINDOWS}-muoto että {$IF DEFINED(MSWINDOWS)-muoto, symbolilla, jota ei koskaan määritellä. Käännä sitten kopio. Kun kaikki 3 828 riviä kääntyvät, olet todistanut, että ei-Windows-polku käyttää yksiköitä, jotka ovat olemassa, kutsuu taustafunktioita täsmäävillä signatuureilla ja viittaa tyyppeihin, jotka ovat näkyvissä. Se ei ole näyttöä siitä, että kuljetus toimii, eikä mikään muu kuin kohdealusta anna sinulle sitä. Se on näyttöä siitä, että haara ei ole jo rikki, mikä on vikamuoto, joka oikeasti kertyy

Kumppanitapa on jättää libcurl-yksikkö itse vapaaksi alustavaheista, jotta se osallistuu tavalliseen Windows-käännökseen, vaikka mikään siellä ei viittaa siihen. Päivittäinen käännös pitää silloin vartioimassa sen syntaksia ja tyyppejään ilmaiseksi. Yksikkö, joka kääntyy vain alustalla, jota sinulla ei ole, on yksikkö ilman mitään kääntäjää tarkistamassa sitä, ja sama päättely pätee ristikkäiskääntäjätyön yli, jonka artikkeli Delphi- ja FPC-ristikkäiskääntäjien sudenkuopat kuvaa

Sen rajoittaminen, mikä palaa takaisin

Aikaleimavastaus on pieni DER-rakenne, eikä mikään kuljetuksessa valvo sitä. Palvelin, joka on murrettu, väärin määritelty tai yksinkertaisesti osoitettu väärään URL:aan, voi palauttaa mielivaltaisen virran, ja asiakas, joka lukee, kunnes yhteys sulkeutuu, kerryttää sitä ilomielin. Molemmat kuljetukset kattovat siksi vastauksen, mikä on oikea paikka rajalle: hylkääminen kuljetuksessa estää ylimitoitettua runkoa varautumasta koskaan, kun taas jäsennystason tarkistus laukeaa vasta, kun muisti on jo sitoutunut

Sama päättely pätee URL:aan. Tausta hyväksyy vain skeemat, joita se pystyy merkityksellisesti puhumaan, joten asetusvirhe epäonnistuu heti selkeällä viestillä sen sijaan, että se passitettaisiin libcurlille tulkittavaksi sillä tavalla, jolla sen protokollatuki sallii

Missä kuljetus istuu allekirjoitustarinassa

Aikaleimaus on pitkäaikaisen validoinnin tarinan ensimmäinen askel, ei koko tarina. Tunnus on kiinnitettävä allekirjoitukseen, validointimateriaali on kirjattava dokumentin security storeen, ja arkisto-aikaleimat on uusittava ennen kuin nykyinen heikkenee. Koko kaari on käsitelty artikkelissa pitkäaikaiset PDF-allekirjoitukset RFC 3161 -aikaleimoilla ja DSS:llä

PDFium VCL:n kaavio RFC 3161 -aikaleimapyynnöstä, joka virtaa DocumentDigestistä BuildTimeStampQueryn ja PostTimeStampQueryn kautta libcurlilla TSA-palvelimelle, DER-vastaus kattoituna kuljetuksessa, sitten AttachTimeStampToken syöttää DSS:n ja arkisto-aikaleimojen uusimisen pitkäaikaisvalidoinnissa
Aikaleimaus on pitkäaikaisen validoinnin tarinan ensimmäinen askel: tunnus on kiinnitettävä, validointimateriaali kirjattava dokumentin security storeen ja arkisto-aikaleimat uusittava ennen kuin nykyinen heikkenee

Kuljetus on myös yksi pala laajempaa siirrettävyysasemaa: artikkelin natiivikirjaston lataaminen millä tahansa kohteella kuvailema natiivikirjaston lataaja käsittelee saman ongelmaluokan itse PDFium-binäärille. Molemmissa tapauksissa kaava on identtinen: sido pieni määrä symboleita dynaamisesti, raportoi tarkasti, mikä ei sidoutunut, äläkä koskaan anna puuttuvan riippuvuuden muuttua linkitysaikaiseksi viaksi, joka estää sovellusta käynnistymästä

Windowsin ja ei-Windowsin aikaleimataustat toimituvat molemmat PDFium Delphi -komponentin mukana, valittuna kohteen eikä konfiguraation perusteella, joten Lazarus-sovellus Linuxilla ja Delphi-sovellus Windowsilla tuottavat saman aikaleimatun allekirjoituksen eri putkistojen kautta