PDFium VCL skickar RFC 3161-tidsstämpelförfrågningar via libcurl på icke-Windows-mål, dynamiskt bunden till åtta symboler, i spegelbild av Windows-backend:ens form som binder till WinHTTP. Två inställningar avgör huruvida transporten är tillförlitlig under last, och hela enheten validerades på en maskin som inte kunde kompilera den för sin målplattform
Tidsstämpling är det som förvandlar en signatur till något som överlever certifikatutgång, och det är en nätsoperation som sitter inuti en signeringsoperation. Den kombinationen gör transportvalet betydelsefullt på ett sätt det vanligen inte är: den körs på en arbetstråd, den talar med en server du inte styr, och ett hänger sig där stannar en signeringspipeline snarare än en sidladdning
Varför libcurl i stället för FPC:s HTTP-klient?
Därför att alternativet släpar in en TLS-stack i repot och sedan låter dig underhålla dess versionsdetektering. Den uppenbara rutten på Free Pascal är fphttpclient med OpenSSL-sockellagret, och den fallerar på detaljerna: FPC 3.2.2:s OpenSSL-bindningar detekterar OpenSSL 3.x opålitligt på de flesta aktuella distributioner, och macOS lägger till LibreSSL-skillnader ovanpå. Det som börjar som ett litet HTTP-anrop blir löpande underhåll av någon annans TLS-ABI
libcurl löser sin egen TLS-backend och validerar kedjor mot plattformens trust store, så Pascal-sidan behöver inget av det. Bindningslagret är åtta symboler. Det antalet är argumentet: en mindre yta mellan din kod och ett rörligt beroende betyder färre ställen för en distributionsuppgradering att bryta dig, och det matchar den befintliga Windows-backend:en, som binder en handfull WinHTTP-ingångspunkter på samma sätt
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;
Att deklarera en C-variadisk funktion i Pascal
curl_easy_setopt och curl_easy_getinfo är variadiska på C-sidan, och Object Pascal har inget sätt att uttrycka det. Angreppssättet som fungerar är att deklarera flera fasta prototyper, en per argumentklass, alla pekande på samma exporterade symbol: en long-tagande variant, en pekare-tagande variant, och så vidare, valda vid anropsplatsen efter vad du faktiskt skickar
Detta är säkert av en specifik anledning värd att förstå snarare än kopiera. Vart och ett av de argumenttyperna skickas i ett heltalsregister under de plattformsanropskonventioner som gäller, vilket är exakt där C-implementationens va_arg läser det. Tricket gäller alltså för heltal, pekare och handtag, och det gäller inte för flyttalsargument, som färdas i andra register. Lägg inte till en double-tagande variant i tron att mönstret generaliserar
// En exporterad symbol, flera fasta prototyper. Varje variant skickar
// sitt argument i ett heltalsregister, vilket är där C-sidan läser
// det. En flyttalsvariant skulle inte fungera och får inte läggas till
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;
Två inställningar som avgör huruvida förfrågan fullföljs
Den första är en explicit tom Expect:-rubrik. libcurl slår på HTTP 100-continue-handskakningen för begärandekroppar över ungefär en kilobyte, och en tidsstämpelförfrågan med ett certifikatbegäran klarar vanligen den tröskeln. Vissa TSA-servrar svarar aldrig på fortsättningen, så klienten väntar ut en full timeout innan den skickar en kropp servern skulle ha accepterat omedelbart. Att skicka en tom Expect:-rubrik undertrycker handskakningen, och förfrågan går igenom på en enda rundresa
Den andra är CURLOPT_NOSIGNAL, som måste sättas. Utan den implementerar libcurl sin namnuppslösningstimeout med SIGALRM, och den mekanismen är inte trådsäker. Signering körs på en arbetstråd, så standardbeteendet är en latent krasch som uppträder under samtidighet och aldrig i ett entrådigt test. Att sätta flaggan stänger av den signalbaserade vägen och kostar bara upplösningstimeoutens granularitet
Båda defekterna delar en profil som gör dem dyra att hitta senare. Ingen av dem dyker upp i ett funktionellt test mot en välskött server på en enda tråd. Båda dyker upp i produktion, mot en viss TSA, under last. När du binder ett nätverksbibliotek, läs vad dess förval antar om din process innan du antar att de matchar
Hur verifierar du kod din kompilator aldrig ser?
Genom att ändå få kompilatorn att se den, via en kontrollerad kopia. Utvecklingsmaskinen här har ingen Linux- eller macOS-korskompilator, så tidsstämpelenhetens icke-Windows-grenar når aldrig kodgeneratorn under en vanlig bygge. Kod som aldrig kompileras är kod som ruttnar tyst: en omdöpning i en delad typ, en ändrad parameterlista, ett tillagt enhetsberoende, och ingen märker det på månader
Tekniken är mekanisk. Kopiera enheten till en temporär katalog, döp om den, och ersätt varje Windows-villkor, både {$IFDEF MSWINDOWS}-formen och {$IF DEFINED(MSWINDOWS)-formen, med en symbol som aldrig definieras. Kompilera sedan kopian. När alla 3 828 rader kompilerar har du bevisat att icke-Windows-vägen använder enheter som finns, anropar backend-funktioner med matchande signaturer och refererar typer som är i scope. Det är inte bevis för att transporten fungerar, och ingenting kortare än målplattformen ger dig det. Det är bevis på att grenen inte redan är sönder, vilket är det fellivet som faktiskt ackumuleras
Den följeslagande vanan är att låta libcurl-enheten själv vara fri från plattformsvakter, så att den deltar i det vanliga Windows-bygget även om ingenting där refererar den. Det dagliga bygget håller sedan vakt över dess syntax och typer gratis. En enhet som bara kompilerar på en plattform du inte har är en enhet ingen kompilator kontrollerar alls, och samma resonemang gäller över hela det korskompilatorarbete som beskrivs i Delphi- och FPC-korskompilatorgropar
Att sätta gränser för det som kommer tillbaka
Ett tidsstämpelsvar är en liten DER-struktur, och ingenting i transporten tvingar fram det. En server som är komprometterad, felkonfigurerad eller bara pekad mot fel URL kan returnera en godtycklig ström, och en klient som läser tills anslutningen stängs ackumulerar den med glädje. Båda transporterna takar därför svaret, vilket är den rätta platsen för gränsen: att avvisa på transportnivå hindrar en överdimensionerad kropp från att någonsin allokeras, medan en kontroll på parsarnivå bara triggas efter att minnet redan förbundits
Samma resonemang gäller URL:en. Backend:en accepterar endast scheman den kan tala meningsfullt, så ett konfigurationsmisstag fallerar omedelbart med ett tydligt meddelande i stället för att lämnas till libcurl att tolka på vilket sätt dess protokollstöd tillåter
Var transporten sitter i signeringshistorien
Tidsstämpling är det första steget i berättelsen om långtidsvalidering snarare än hela den. Tokenen måste fästas vid signaturen, valideringsmaterialet måste registreras i dokumentets security store, och arkivtidsstämplar måste förnyas innan den aktuella försvagas. Hela den bågen tas upp i långtids-PDF-signaturer med RFC 3161-tidsstämplar och DSS
Transporten är också en bit av en bredare portabilitetsposition: den nativa biblioteksladdare som beskrivs i att läsa in det nativa biblioteket på vilket mål som helst hanterar samma problemklass för PDFium-binären själv. I båda fallen är mönstret identiskt: binda ett litet antal symboler dynamiskt, rapportera precis vad som misslyckades att binda, och låt aldrig ett saknat beroende bli ett länktidsfel som hindrar applikationen från att starta
Windows- och icke-Windows-tidsstämpelbackend:arna medföljer båda PDFium Delphi component, valda efter mål snarare än efter konfiguration, så en Lazarus-applikation på Linux och en Delphi-applikation på Windows producerar samma tidsstämplade signatur genom olika rörledningar