Tehnički članak

libcurl timestamp backend za PDFium VCL na FPC-u

PDFium VCL šalje RFC 3161 timestamp zahteve kroz libcurl na ne-Windows metama, dinamički vezan za osam simbola, odražavajući oblik Windows backend-a koji se vezuje za WinHTTP. Dva podešavanja opcija odlučuju da li je transport pouzdan pod opterećenjem, i cela jedinica validirana je na mašini koja je nije mogla kompajlirati za svoju ciljnu platformu

Timestamping je ono što od potpisa pravi nešto što preživi istek sertifikata, i to je mrežna operacija smeštena unutar operacije potpisivanja. Ta kombinacija čini izbor transporta kobnim na način na koji obično nije: izvršava se na radnom thread-u, razgovara sa serverom koji ne kontrolišete, i zaglavljivanje tamo zastaje potpisni pipeline, a ne učitavanje strane

Zašto libcurl, a ne FPC HTTP klijent?

Zato što alternativa vuče TLS stek u repozitorijum, a vas onda tera da održavate njegovu detekciju verzije. Očigledna ruta na Free Pascal-u je fphttpclient sa OpenSSL socket slojem, i pada na detaljima: FPC 3.2.2 OpenSSL bindingi nepouzdano detektuju OpenSSL 3.x na većini trenutnih distribucija, a macOS dodaje LibreSSL razlike preko toga. Ono što počne kao mali HTTP poziv postaje stalno održavanje tuđeg TLS ABI-ja

libcurl razrešava sopstveni TLS backend i validira lance naspram platformskog trust store-a, pa Pascal strana ne treba ništa od toga. Sloj vezivanja je osam simbola. Taj broj je argument: manja površina između vašeg koda i pokretne zavisnosti znači manje mesta gde nadogradnja distribucije može da vas slomi, i poklapa se sa postojećim Windows backend-om, koji isto tako vezuje šaku WinHTTP ulaznih tačaka

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;

Deklarisanje C variadic funkcije u Pascal-u

curl_easy_setopt i curl_easy_getinfo su variadic na C strani, a Object Pascal nema načina da to izrazi. Pristup koji radi jeste da deklarišete nekoliko fiksnih prototipova, jedan po klasi argumenata, sve usmerene na isti izvezeni simbol: varijanta koja uzima long, varijanta koja uzima pointer, i tako dalje, birane na mestu poziva prema tome šta stvarno prosleđujete

Ovo je bezbedno iz konkretnog razloga koji vredi razumeti, a ne samo kopirati. Svaki od tih tipova argumenata prosleđuje se u celobrojnom registru pod platformskim calling konvencijama na snazi, što je baš mesto iz koga C implementacija va_arg čita. Trik zato važi za cele brojeve, pointere i handle-ove, i ne važi za floating-point argumente, koji putuju u drugim registrima. Nemojte dodati varijantu koja uzima double u pretpostavci da se obrazac generališe

// Jedan izvezeni simbol, nekoliko fiksnih prototipova. Svaka varijanta
// prosleđuje argument u celobrojnom registru, što je mesto gde C strana
// čita. Floating-point varijanta ne bi radila i ne sme se dodati
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;

Dva podešavanja koja odlučuju da li se zahtev dovrši

Prvo je eksplicitno prazan Expect: zaglavlje. libcurl uključuje HTTP 100-continue rukovanje za tela zahteva preko otprilike jednog kilobajta, i timestamp upit sa zahtevom za sertifikat obično pređe taj prag. Neki TSA serveri nikada ne odgovaraju na nastavak, pa klijent odčeka pun istek vremena pre nego što pošalje telo koje bi server odmah prihvatio. Slanje praznog Expect: zaglavlja potiskuje rukovanje, i zahtev prođe u jednom povratnom krugu

Drugo je CURLOPT_NOSIGNAL, koje mora biti postavljeno. Bez njega libcurl implementira svoj istek vremena razrešavanja imena korišćenjem SIGALRM, i taj mehanizam nije thread-safe. Potpisivanje radi na radnom thread-u, pa je podrazumevano ponašanje latentan pad koji se pojavi pod konkurencijom i nikada u testu sa jednom niti. Postavljanje zastavice isključuje putanju zasnovanu na signalima i košta samo granularnost isteka vremena resolver-a

Oba defekta dele profil koji ih kasnije čini skupim za nalaz. Ni jedan se ne pojavi u funkcionalnom testu naspram dobroćudnog servera na jednoj niti. Oba se pojave u produkciji, naspram jednog konkretnog TSA, pod opterećenjem. Kad vezujete mrežnu biblioteku, pročitajte šta njeni default-i pretpostavljaju o vašem procesu pre nego što pretpostavite da se poklapaju

Dijagram libcurl timestamp transporta PDFium VCL koji prikazuje curl_easy_setopt deklarisan kao fiksni long i pointer Pascal prototipovi koji prosleđuju argumente u celobrojnim registrima, prazno Expect zaglavlje koje potiskuje HTTP 100-continue rukovanje, CURLOPT_NOSIGNAL koji uklanja SIGALRM putanju na radnim thread-ovima, i odzivni limit na nivou transporta
Dva podešavanja odlučuju da li se zahtev dovrši: prazno Expect zaglavlje izbegava servere koji nikada ne odgovaraju na nastavak, a NOSIGNAL drži istek vremena razrešavanja imena van signalne putanje dok potpisivanje radi na radnom thread-u

Kako verifikujete kod koji vaš kompajler nikada neće videti?

Tako što ipak naterate kompajler da ga vidi, kroz kontrolisanu kopiju. Razvojna mašina ovde nema Linux ili macOS cross-kompajler, pa ne-Windows grane jedinice za timestamping nikada ne stižu do generatora koda tokom normalnog build-a. Kod koji se nikada ne kompajlira je kod koji tiho trune: preimenovanje u deljenom tipu, promenjen spisak parametara, dodata unit zavisnost, i niko ne primećuje mesečima

Tehnika je mehanička. Kopirajte unit u privremeni direktorijum, preimenujte ga, i zamenite svaki Windows uslovni, i oblik {$IFDEF MSWINDOWS} i oblik {$IF DEFINED(MSWINDOWS), simbolom koji nikada nije definisan. Pa kompajlirajte kopiju. Kad se svih 3.828 linija kompajlira, dokazali ste da ne-Windows putanja koristi unit-e koji postoje, zove backend funkcije sa poklapajućim potpisima, i referencira tipove koji su u opsegu. To nije dokaz da transport radi, i ništa kraće od ciljne platforme vam to neće dati. To je dokaz da grana već nije polomljena, što je režim otkaza koji se zapravo gomila

Prateća navika je da libcurl unit ostane sam bez platformskih guard-ova, pa učestvuje u običnom Windows build-u iako ga tamo ništa ne referencira. Dnevni build tada besplatno nastavlja da čuva njegovu sintaksu i tipove. Unit koji se kompajlira samo na platformi koju nemate je unit koji nijedan kompajler uopšte ne proverava, i isto rasuđivanje važi kroz celi cross-kompajlerski rad opisan u članku o zamkama Delphi i FPC cross-kompajlera

Ograničavanje onoga što se vraća

Timestamp odziv je mala DER struktura, i ništa u vezi transporta to ne sprovodi. Kompromitovan, pogrešno podešen ili jednostavno na pogrešan URL usmeren server može vratiti proizvoljan tok, i klijent koji čita dok se veza ne zatvori rado će ga gomilati. Oba transporta zato ograničavaju odziv, i to je pravo mesto za limit: odbijanje na transportu sprečava da se preterilo telo ikada alocira, dok provera na nivou parser-a opali tek posle nego što je memorija već odobrena

Isto rasuđivanje važi za URL. Backend prihvata samo sheme koje ume smisleno da govori, pa greška u konfiguraciji padne odmah sa jasnom porukom umesto da se uruči libcurl-u da je tumači kako god njegova podrška protokola dopušta

Gde transport sedi u priči o potpisivanju

Timestamping je prvi korak priče o dugoročnoj validaciji, a ne cela priča. Token mora biti zakačen za potpis, materijal validacije mora biti zabeležen u document security store-u, i arhivski timestamp-ovi moraju se obnavljati pre nego što trenutni oslabi. Ceo taj luk pokriven je u članku o dugoročnim PDF potpisima sa RFC 3161 timestamp-ovima i DSS-om

PDFium VCL dijagram RFC 3161 timestamp zahteva koji teče od DocumentDigest kroz BuildTimeStampQuery i PostTimeStampQuery preko libcurl-a do TSA servera, DER odziv ograničen na transportu, pa AttachTimeStampToken hrani DSS i obnavljanje arhivskih timestamp-ova u dugoročnoj validaciji
Timestamping je prvi korak priče o dugoročnoj validaciji: token mora biti zakačen, materijal validacije zabeležen u document security store-u, a arhivski timestamp-ovi obnovljeni pre nego što trenutni oslabi

Transport je takođe jedan deo šireg stava o portabilnosti: nativni učitavač biblioteka opisan u članku o učitavanju nativne biblioteke na bilo koju metu rešava istu klasu problema za sam PDFium binarni artefakt. U oba slučaja obrazac je identičan: dinamički vezati mali broj simbola, precizno prijaviti šta se nije vezalo, i nikada ne dozvoliti da nedostajuća zavisnost postane greška u trenutku povezivanja koja sprečava aplikaciju da se pokrene

Windows i ne-Windows timestamp backend-i oboje stižu uz PDFium Delphi komponentu, birani po meti, a ne po konfiguraciji, pa Lazarus aplikacija na Linux-u i Delphi aplikacija na Windows-u proizvode isti potpis sa vremenskim žigom kroz različitu infrastrukturu