PDFium VCL stuurt RFC 3161 timestamp-verzoeken via libcurl op niet-Windows-targets, dynamisch gebonden aan acht symbolen, als spiegelbeeld van de Windows-backend die aan WinHTTP bindt. Twee optie-instellingen bepalen of het transport onder belasting betrouwbaar is, en de hele unit werd gevalideerd op een machine die hem voor zijn targetplatform niet eens kon compileren
Timestamping is wat van een handtekening iets maakt dat certificaatverloop overleeft, en het is een netwerkoperatie die binnenin een ondertekeningsoperatie zit. Die combinatie maakt de transportkeuze consequent op een manier die hij gewoonlijk niet is: hij draait op een worker thread, hij praat met een server die u niet beheert, en een hang daar blokkeert een ondertekeningspipeline in plaats van een paginalading
Waarom libcurl in plaats van de FPC HTTP client?
Omdat het alternatief een TLS-stack de repository insleept en u vervolgens zijn versiedetectie laat onderhouden. De voor de hand liggende route op Free Pascal is fphttpclient met de OpenSSL-socketlaag, en die faalt op de details: de FPC 3.2.2 OpenSSL-bindings detecteren OpenSSL 3.x onbetrouwbaar op de meeste huidige distributies, en macOS voegt daar LibreSSL-verschillen bovenop. Wat begint als een kleine HTTP-aanroep wordt voortdurend onderhoud van het TLS ABI van iemand anders
libcurl resolved zijn eigen TLS-backend en valideert chains tegen de platform trust store, dus de Pascal-kant heeft dat alles niet nodig. De bindinglaag is acht symbolen. Dat aantal is het argument: een kleiner oppervlak tussen uw code en een bewegelijke dependency betekent minder plekken waar een distributie-upgrade u kan breken, en het matcht de bestaande Windows-backend, die op dezelfde manier een handjevol WinHTTP-entrypoints bindt
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;
Een C-variadische functie declareren in Pascal
curl_easy_setopt en curl_easy_getinfo zijn variadisch aan de C-kant, en Object Pascal heeft geen manier om dat uit te drukken. De aanpak die werkt, is meerdere vaste prototypes declareren, één per argumentklasse, allemaal wijzend naar hetzelfde geëxporteerde symbool: een long-variant, een pointer-variant, enzovoort, gekozen op de call site door wat u werkelijk doorgeeft
Dit is veilig om een specifieke reden die het begrijpen waard is in plaats van kopiëren. Elk van die argumenttypes wordt onder de geldende platform calling conventions doorgegeven in een integer-register, en dat is precies waar de C-implementatie va_arg hem vandaan leest. De truc geldt daardoor voor integers, pointers en handles, en hij geldt niet voor floating-point-argumenten, die in andere registers reizen. Voeg geen double-variant toe op de aanname dat het patroon veralgemeent
// Eén geëxporteerd symbool, meerdere vaste prototypes. Elke variant geeft zijn
// argument door in een integer-register, en dat is waar de C-kant hem leest.
// Een floating-point-variant zou niet werken en mag niet worden toegevoegd
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;
Twee instellingen die bepalen of het verzoek afloopt
De eerste is een expliciet lege Expect:-header. libcurl zet de HTTP 100-continue-handshake aan voor request bodies van ruwweg een kilobyte en daarboven, en een timestamp-query met een certificaatverzoek komt doorgaans over die drempel. Sommige TSA-servers antwoorden nooit op de continuation, dus de client wacht een volledige timeout uit voordat hij een body verstuurt die de server onmiddellijk geaccepteerd zou hebben. Een lege Expect:-header versturen onderdrukt de handshake, en het verzoek gaat in één round trip door
De tweede is CURLOPT_NOSIGNAL, die gezet moet worden. Zonder die implementeert libcurl zijn name-resolution timeout met SIGALRM, en dat mechanisme is niet thread-safe. Ondertekenen draait op een worker thread, dus het standaardgedrag is een latente crash die onder gelijktijdigheid verschijnt en nooit in een single-threaded test. De flag zetten schakelt de signaalgebaseerde route uit en kost alleen de resolutiegranulariteit van de resolver-timeout
Beide defecten delen een profiel dat ze later duur maakt om te vinden. Geen van beide verschijnt in een functionele test tegen een welwillende server op één thread. Beide verschijnen in productie, tegen één specifieke TSA, onder belasting. Als u een netwerklibrary bindt, lees dan wat zijn defaults over uw proces aannemen voordat u aanneemt dat ze matchen
Hoe verifieert u code die uw compiler nooit zal zien?
Door de compiler hem toch te laten zien, via een gecontroleerde kopie. De ontwikkel-machine hier heeft geen Linux- of macOS-cross-compiler, dus de niet-Windows-takken van de timestamping-unit bereiken de codegenerator nooit tijdens een normale build. Code die nooit wordt gecompileerd is code die stilletjes rot: een hernoeming in een gedeeld type, een veranderde parameterlijst, een toegevoegde unit-afhankelijkheid, en niemand merkt het maandenlang
De techniek is mechanisch. Kopieer de unit naar een tijdelijke directory, hernoem hem en vervang elke Windows-conditional, zowel de {$IFDEF MSWINDOWS}-vorm als de {$IF DEFINED(MSWINDOWS)-vorm, door een symbool dat nooit gedefinieerd is. Compileer daarna de kopie. Als alle 3.828 regels compileren, heeft u bewezen dat het niet-Windows-pad units gebruikt die bestaan, backendfuncties aanroept met matchende signaturen en types raakt die in scope zijn. Dat is geen bewijs dat het transport werkt, en niets minder dan het targetplatform geeft u dat. Het is bewijs dat de tak niet al kapot is, en dat is de faalmodus die werkelijk accumuleert
De begeleidende gewoonte is de libcurl-unit zelf vrij van platformguards te houden, zodat hij deelneemt aan de gewone Windows-build zelfs als daar niets naar hem verwijst. De dagelijkse build bewaakt dan gratis zijn syntaxis en types. Een unit die alleen compileert op een platform dat u niet heeft, is een unit waar helemaal geen compiler op kijkt, en dezelfde redenering geldt in het hele cross-compilerwerk dat staat beschreven in Delphi- en FPC-cross-compiler-valkuilen
Begrenzen van wat er terugkomt
Een timestamp-respons is een kleine DER-structuur, en niets aan het transport dwingt dat af. Een server die is gecompromitteerd, verkeerd geconfigureerd of simpelweg naar de verkeerde URL wijzend, kan een willekeurige stream teruggeven, en een client die leest tot de verbinding sluit, stapelt hem braaf op. Beide transporten begrenzen daarom de respons, en dat is de juiste plek voor de limiet: weigeren op transportniveau voorkomt dat een te grote body ooit wordt gealloceerd, terwijl een check op parserniveau pas afgaat nadat het geheugen is vastgelegd
Dezelfde redenering geldt voor de URL. De backend accepteert alleen schemes die hij zinvol kan spreken, dus een configuratiefout faalt onmiddellijk met een duidelijke melding in plaats van aan libcurl te worden overgelaten om te interpreteren op welke manier zijn protocolondersteuning maar toelaat
Waar het transport zit in het ondertekeningsverhaal
Timestamping is de eerste stap van het langetermijnvalidatieverhaal in plaats van het hele verhaal. Het token moet aan de handtekening worden gehangen, het validatiemateriaal moet in de document security store worden vastgelegd, en archive timestamps moeten worden vernieuwd voordat de huidige verzwakt. Die hele boog staat in langetermijn-PDF-handtekeningen met RFC 3161 timestamps en de DSS
Het transport is ook één stuk van een bredere portabiliteitspositie: de native library loader beschreven in de native library laden op elke target behandelt dezelfde probleemklasse voor de PDFium-binary zelf. In beide gevallen is het patroon identiek: een klein aantal symbolen dynamisch binden, precies rapporteren wat niet bindt, en nooit toestaan dat een ontbrekende dependency een link-time failure wordt die de applicatie verhindert te starten
De Windows- en niet-Windows-timestamp-backends worden beide geleverd met de PDFium Delphi component, gekozen per target in plaats van per configuratie, zodat een Lazarus-applicatie op Linux en een Delphi-applicatie op Windows dezelfde timestamp-handtekening produceren via verschillende leidingen