PDFium VCL pošilja zahteve po časovnem žigu RFC 3161 skozi libcurl na ne-Windows ciljih, dinamično vezane na osem simbolov, v zrcaljenju oblike zaledja Windows, ki se veže na WinHTTP. Dve nastavitvi možnosti odločita, ali je prenos zanesljiv pod obremenitvijo, celotna enota pa je bila validirana na stroju, ki je za svojo ciljno platformo ni mogel prevesti
Časovno žigosanje je tisto, ki podpis spremeni v nekaj, kar preživi potek veljavnosti certifikata, in je omrežna operacija, ki sede znotraj podpisne operacije. Ta kombinacija naredi izbiro prenosa odločilno na način, kot običajno ni: teče na delovni niti, govori s strežnikom, ki ga ne nadzorujete, in zasek tam zablokira podpisni cevovod in ne nalaganje strani
Zakaj libcurl in ne odjemalec HTTP FPC?
Ker alternativa v repozitorij povleče sklad TLS in vas potem prisili, da vzdržujete njegovo zaznavanje verzij. Očitna pot na Free Pascal je fphttpclient s plastjo vtičnic OpenSSL in spodleti na podrobnostih: vezi OpenSSL FPC 3.2.2 nezanesljivo zaznavajo OpenSSL 3.x na večini trenutnih distribucij, macOS pa doda še razlike LibreSSL. Kar se začne kot majhen klic HTTP, postane nenehno vzdrževanje tujega TLS ABI
libcurl razreši svoje lastno zaledje TLS in validira verige proti shrambi zaupanja platforme, tako da Pascal stran tega ne potrebuje. Vezalna plast je osem simbolov. To štetje je argument: manjša površina med vašo kodo in gibljivo odvisnostjo pomeni manj mest, kjer vas lahko nadgradnja distribucije zlomi, in se ujema z obstoječim zaledjem Windows, ki na enak način veže peščico vstopnih točk WinHTTP
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;
Razglašanje variadične funkcije C v Pascalu
curl_easy_setopt in curl_easy_getinfo sta na strani C variadična in Object Pascal nima načina, kako to izraziti. Pristop, ki deluje, je razglasiti več fiksnih prototipov, enega na razred argumentov, vse kazajoče na isti izvoženi simbol: različico, ki jemlje long, različico, ki jemlje kazalec, in tako naprej — izbrano na mestu klica glede na to, kaj dejansko podajate
To je varno iz določenega razloga, ki ga vredi razumeti in ne kopirati. Vsak od teh tipov argumentov se poda v celoštevilskem registru pod klicnimi konvencijami platforme v igri — natanko tam, kjer ga implementacija C va_arg prebere. Trik zato velja za cela števila, kazalce in ročaje in ne velja za plavajoče argumente, ki potujejo v drugih registrih. Različice, ki jemlje double, ne dodajajte v prepričanju, da se vzorec posplošuje
// En izvoženi simbol, več fiksnih prototipov. Vsaka različica poda
// svoj argument v celoštevilskem registru, kjer ga bere stran C.
// Plavajoča različica ne bi delovala in je ne smete 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;
Dve nastavitvi, ki odločita, ali zahteva zaključi
Prva je izrecna prazna glava Expect:. libcurl vklopi izmenjavo HTTP 100-continue za telesa zahtev čez približno en kilobajt, poizvedba po časovnem žigu z zahtevo po certifikatu pa ta prag običajno preseže. Nekateri strežniki TSA nikoli ne odgovorijo na nadaljevanje, tako da odjemalec izčrpa poln časovni zamik, preden pošlje telo, ki bi ga strežnik sprejel takoj. Pošiljanje prazne glave Expect: zatre izmenjavo in zahteva gre skozi v enem povratnem skoku
Druga je CURLOPT_NOSIGNAL, ki jo je treba nastaviti. Brez nje libcurl implementira svoj časovni zamik razreševanja imen z SIGALRM in ta mehanizem ni thread-safe. Podpisovanje teče na delovni niti, tako da je privzeto vedenje latentno sesutje, ki se pojavi pod sočasnostjo in nikoli v enonitnem testu. Nastavitev zastavice onemogoči pot na osnovi signalov in stane le granularnost časovnega zamika razreševalnika
Oba pomanjkljivosti si delita profil, ki ju naredi drago najti kasneje. Nobena se ne pokaže v funkcionalnem testu proti dobro utečenemu strežniku na eni niti. Obe se pojavita v produkciji, proti enemu konkretnemu TSA, pod obremenitvijo. Ko vežete omrežno knjižnico, preberite, kaj njeni privzetki predpostavljajo o vašem procesu, preden predpostavite, da se ujemajo
Kako preverite kodo, ki je vaš prevajalnik nikoli ne bo videl?
Tako, da prevajalnik vseeno vidi, skozi nadzorovan izvod. Razvojni stroj tukaj nima prečnega prevajalnika za Linux ali macOS, tako da veje ne-Windows enote za časovno žigosanje med normalno gradnjo nikoli ne pridejo do generatorja kode. Koda, ki se nikoli ne prevede, je koda, ki tiho gni: preimenovanje v deljenem tipu, spremenjen seznam parametrov, dodana odvisnost enote — in nihče ne opazi mesece
Tehnika je mehanična. Kopirajte enoto v začasni imenik, jo preimenujte in zamenjajte vsak Windows pogoj — tako obliko {$IFDEF MSWINDOWS} kot obliko {$IF DEFINED(MSWINDOWS) — s simbolom, ki nikoli ni definiran. Nato prevedite izvod. Ko se vseh 3.828 vrstic prevede, ste dokazali, da pot ne-Windows uporablja enote, ki obstajajo, kliče funkcije zaledja z ujemajočimi se podpisi in se sklicuje na tipe, ki so v obsegu. To ni dokaz, da prenos deluje, in nič krajšega od ciljne platforme vam tega ne da. Je dokaz, da veja še ni pokvarjena — to pa je način odpovedi, ki se dejansko kopiči
Spremna navada je pustiti samo enoto libcurl brez platformskih stražarjev, tako da sodeluje v običajni gradnji Windows, čeprav se nanjo tam nič ne sklicuje. Dnevna gradnja potem zastonj še naprej straži njeno skladnjo in tipe. Enota, ki se prevede le na platformi, ki je nimate, je enota brez nobenega prevajalnika, ki bi jo preverjal, isto razmišljanje pa velja čez delo čez prevajalnike, opisano v pasteh prečnih prevajalnikov Delphi in FPC
Omejevanje tega, kar se vrne
Odgovor časovnega žiga je majhna struktura DER in nič pri prenosu tega ne uveljavi. Strežnik, ki je ogrožen, napačno nastavljen ali pa preprosto usmerjen na napačen URL, lahko vrne poljuben tok in odjemalec, ki bere do zaprtja povezave, ga bo z veseljem nakopičil. Oba prenosa zato odgovor odrežeta, kar je pravo mesto za mejo: zavrnitev na prenosu prepreči, da bi se predimenzionirano telo sploh kadarkoli dodelilo, preverjanje na ravni razčlenjevalnika pa se sproži šele, ko je pomnilnik že zavezan
Isto razmišljanje velja za URL. Zaledje sprejema le sheme, ki jih zna smiselno govoriti, tako da napaka konfiguracije takoj spodleti z jasnim sporočilom, namesto da bi bila predana libcurlu, naj jo razlaga na kakršen koli način, ki mu njegova podpora protokolov dovoli
Kje prenos sede v zgodbi podpisovanja
Časovno žigosanje je prvi korak zgodbe dolgoročne validacije in ne celotna zgodba. Žeton se mora pripeti podpisu, validacijsko gradivo se mora zabeležiti v shrambo varnosti dokumenta, arhivski časovni žigi pa se morajo obnoviti, preden trenutni oslabi. Celoten lok je pokrit v dolgoročnih podpisih PDF s časovnimi žigi RFC 3161 in DSS
Prenos je tudi en kos širšega položaja prenosljivosti: nalagalnik nativnih knjižnic, opisan v nalaganju nativne knjižnice na katerem koli cilju, obravnava isti razred težav za samo binarko PDFium. V obeh primerih je vzorec identičen — vezati majhno število simbolov dinamično, točno sporočiti, kaj se ni vezalo, in nikoli ne dovoliti, da bi manjkajoča odvisnost postala spodletel ob povezovanju, ki prepreči aplikaciji zagon
Zaledji časovnih žigov Windows in ne-Windows oba prideta z PDFium Delphi component, izbran po cilju in ne po konfiguraciji, tako da Lazarus aplikacija na Linuxu in Delphi aplikacija na Windows proizvedeta isti podpis s časovnim žigom skozi različne instalacije