PDFium VCL šalje RFC 3161 timestamp zahtjeve kroz libcurl na ne-Windows ciljevima, dinamički vezan na osam simbola, preslikavajući oblik Windows backenda koji se vezuje na WinHTTP. Dvije opcije postavke odlučuju je li transport pouzdan pod opterećenjem, i cijeli unit je validiran na mašini koja ga nije mogla kompajlirati za svoju ciljnu platformu
Timestamping je ono što potpis pretvara u nešto što preživljava istek certifikata, i to je mrežna operacija koja sjedi unutar potpisne operacije. Ta kombinacija čini izbor transporta sudbonosnim na način koji obično nije: trči na worker threadu, razgovara s poslužiteljem kojeg ne kontrolirate, i vješanje tamo zaustavlja signing pipeline umjesto učitavanja stranice
Zašto libcurl umjesto FPC HTTP klijenta?
Jer alternativa vuče TLS stack u repozitorij i onda vas natjera da održavate njegovu detekciju verzije. Očigledna ruta na Free Pascalu je fphttpclient s OpenSSL socket slojem, i pade na detaljima: FPC 3.2.2 OpenSSL bindingi nepouzdano detektiraju OpenSSL 3.x na većini trenutnih distribucija, a macOS doda LibreSSL razlike na vrh. Ono što počne kao mali HTTP poziv postaje stalno održavanje tuđeg TLS ABI-ja
libcurl razrješuje svoj vlastiti TLS backend i validira lance protiv platformskog trust storea, pa Pascal strana ne treba ništa od toga. Binding sloj je osam simbola. Taj broj je argument: manja površina između vašeg koda i pokretne dependencije znači manje mjesta gdje vas distribucijska nadogradnja može slomiti, i slaže se s postojećim Windows backendom, koji na isti način vezuje šaku WinHTTP entry pointova
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;
Deklariranje C variadic funkcije u Pascalu
curl_easy_setopt i curl_easy_getinfo su variadic na C strani, i Object Pascal nema načina to izraziti. Pristup koji radi je deklarirati nekoliko fiksnih prototipova, jedan po klasi argumenta, sve usmjerene na isti izvezeni simbol: varijanta koja uzima long, varijanta koja uzima pokazivač, i tako dalje, birana na call siteu prema onome što stvarno predajete
To je sigurno iz specifičnog razloga koji vrijedi razumjeti umjesto kopirati. Svaki od tih tipova argumenata predaje se u integer registru pod platformskim calling konvencijama koje su u igri, a to je točno odakle C implementacija va_arg čita. Trik zato drži za integere, pokazivače i handleove, i ne drži za floating-point argumente, koji putuju u drugim registrima. Ne dodajte varijantu koja uzima double u pretpostavci da se obrazac generalizira
// Jedan izvezeni simbol, nekoliko fiksnih prototipova. Svaka varijanta
// predaje argument u integer registru, odakle ga C strana čita.
// Floating-point varijanta ne bi radila i ne smije 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;
Dvije postavke koje odlučuju dovršava li se zahtjev
Prva je eksplicitno prazan Expect: header. libcurl uključuje HTTP 100-continue handshake za tijela zahtjeva iznad otprilike kilobajta, a timestamp upit s certifikatnim zahtjevem obično prijeđe taj prag. Neki TSA poslužitelji nikad ne odgovore na nastavak, pa klijent isčekuje puni timeout prije nego pošalje tijelo koje bi poslužitelj odmah prihvatio. Slanje praznog Expect: headera potiskuje handshake, i zahtjev prođe u jednom round tripu
Druga je CURLOPT_NOSIGNAL, koja mora biti postavljena. Bez nje libcurl implementira svoj name-resolution timeout pomoću SIGALRM-a, i taj mehanizam nije thread-safe. Signing trči na worker threadu, pa je defaultno ponašanje latentni crash koji se pojavi pod konkurencijom i nikad u jednothreadnom testu. Postavljanje zastavice isključuje signalni put i košta samo granularnost resolver timeouta
Oba defekta dijele profil koji ih kasnije skupo pronalazi. Nijedan se ne pojavi u funkcionalnom testu protiv dobro odgojenog poslužitelja na jednom threadu. Oba se pojave u produkciji, protiv jednog konkretnog TSA-e, pod opterećenjem. Kad vezujete networking biblioteku, pročitajte što njezini defaulti pretpostavljaju o vašem procesu prije nego pretpostavite da se poklapaju
Kako verificirati kod koji vaš kompajler nikad neće vidjeti?
Tako da kompajler ipak vidi kod, kroz kontroliranu kopiju. Razvojna mašina ovdje nema Linux ili macOS cross-compiler, pa ne-Windows grane timestamping unita nikad ne stignu do generatora koda tijekom normalnog builda. Kod koji se nikad ne kompajlira je kod koji tiho trune: preimenovanje u dijeljenom tipu, promijenjen popis parametara, dodana unit dependencija, i nitko ne primijeti mjesece
Tehnika je mehanička. Kopirajte unit u privremeni direktorij, preimenujte ga, i zamijenite svaki Windows uvjetni, i {$IFDEF MSWINDOWS} oblik i {$IF DEFINED(MSWINDOWS) oblik, simbolom koji nikad nije definiran. Zatim kompajlirajte kopiju. Kad se svih 3.828 linija kompajlira, dokazali ste da ne-Windows put koristi unite koji postoje, zove backend funkcije s odgovarajućim significama i referencira tipove koji su u dosegu. 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 slomljena, a to je failure mod koji se stvarno nakuplja
Prateća navika je ostaviti libcurl unit samog bez platformskih guardova, pa sudjeluje u običnom Windows buildu iako ga tamo ništa ne referencira. Dnevni build onda besplatno i dalje čuva njegovu sintaksu i tipove. Unit koji se kompajlira samo na platformi koju nemate je unit bez ijednog kompajlera koji ga provjerava, i isto se razmišljanje primjenjuje na cijeli cross-compiler posao opisan u Delphi i FPC cross-compiler zamkama
Ograničavanje onoga što se vraća
Timestamp odgovor je mala DER struktura, i ništa oko transporta to ne nameće. Poslužitelj koji je kompromitiran, pogrešno konfiguriran ili jednostavno usmjeren na krivi URL može vratiti proizvoljan stream, i klijent koji čita do zatvaranja veze će ga slobodno nagomilati. Oba transporta zato postavljaju strop na odgovor, što je pravo mjesto za limit: odbijanje na transportu sprječava da pretjerano veliko tijelo ikad bude alocirano, dok parser-level provjera puca tek nakon što je memorija obračunana
Isto razmišljanje vrijedi i za URL. Backend prima samo sheme koje može smisleno govoriti, pa konfiguracijska greška pade odmah s jasnom porukom umjesto da se preda libcurlu da je protumači na koji god način njegova podrška protokola dopušta
Gdje transport sjedi u priči o potpisivanju
Timestamping je prvi korak priče o dugoročnoj validaciji umjesto cijele priče. Token se mora prikopčati na potpis, validacijski materijal mora se zabilježiti u document security store, i archive timestampovi se moraju obnavljati prije nego trenutni oslabi. Cijeli je taj luk pokriven u dugoročnim PDF potpisima s RFC 3161 timestampovima i DSS-om
Transport je također jedan dio šire pozicije prenosivosti: native library loader opisan u učitavanju native biblioteke na bilo kojem cilju rješava istu klasu problema za sam PDFium binary. U oba je slučaja obrazac identičan, vezati mali broj simbola dinamički, izvještati precizno što se nije vezalo, i nikad dopustiti da nedostajuća dependencija postane link-time neuspjeh koji sprječava aplikaciju da krene
Windows i ne-Windows timestamp backendi oboje isporučuju uz PDFium Delphi komponentu, birani po cilju umjesto po konfiguraciji, pa Lazarus aplikacija na Linuxu i Delphi aplikacija na Windowsima proizvode isti timestampirani potpis kroz različitu pozadinsku infrastrukturu