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
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
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