PDFium VCL sender RFC 3161-tidsstemmelforespørsler gjennom libcurl på ikke-Windows-mål, dynamisk bundet til åtte symboler, speilet formen til Windows-backend-en som binder til WinHTTP. To innstillinger avgjør om transporten er pålitelig under last, og hele enheten ble validert på en maskin som ikke kunne kompilere den for målplattformen sin
Tidsstempling er det som gjør en signatur om til noe som overlever sertifikatutløp, og det er en nettverksoperasjon som sitter inne i en signeringsoperasjon. Den kombinasjonen gjør transportvalget konsekvensrikt på en måte det vanligvis ikke er: det kjører på en arbeidertråd, det snakker med en server du ikke kontrollerer, og et heng der setter en signeringsrørledning i stampe snarere enn en sidelasting
Hvorfor libcurl i stedet for FPC HTTP-klienten?
Fordi alternativet sleper en TLS-stakk inn i repositoriet og deretter får deg til å vedlikeholde versjonsdeteksjonen dens. Den åpenbare ruten på Free Pascal er fphttpclient med OpenSSL-socket-laget, og den feiler på detaljene: FPC 3.2.2 OpenSSL-bindingene detekterer OpenSSL 3.x upålitelig på de fleste nåværende distribusjoner, og macOS legger LibreSSL-forskjeller oppå. Det som starter som et lite HTTP-kall, blir løpende vedlikehold av en annens TLS ABI
libcurl løser sin egen TLS-backend og validerer kjeder mot plattformens trust store, så Pascal-siden trenger ingen av det. Bindingssjiktet er åtte symboler. Det antallet er argumentet: en mindre overflate mellom koden din og en bevegelig avhengighet betyr færre steder en distribusjonsoppgradering kan knekke deg, og det matcher den eksisterende Windows-backend-en, som binder en neve WinHTTP-inngangspunkter på samme måte
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;
Å deklarere en C-variadisk funksjon i Pascal
curl_easy_setopt og curl_easy_getinfo er variadiske på C-siden, og Object Pascal har ingen måte å uttrykke det på. Tilnærmingen som fungerer, er å deklarere flere faste prototyper, én per argumentklasse, alle pekende på samme eksporterte symbol: en long-takende variant, en peker-takende variant, og så videre, valgt ved kallstedet etter hva du faktisk sender
Dette er trygt av en spesifikk grunn verdt å forstå snarere enn å kopiere. Hver av de argumenttypene sendes i et heltallsregister under plattformens kallekonvensjoner som er i spill, noe som er nøyaktig der C-implementasjonens va_arg leser den fra. Trikset gjelder derfor for heltall, pekere og håndtak, og det gjelder ikke for flyttallsargumenter, som reiser i andre registre. Ikke legg til en double-takende variant i den antakelsen at mønsteret generaliserer
// Ett eksportert symbol, flere faste prototyper. Hver variant sender
// argumentet sitt i et heltallsregister, noe som er der C-siden leser
// det. En flyttalls-variant ville ikke fungert og må ikke legges til
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;
To innstillinger som avgjør om forespørselen fullføres
Den første er en eksplisitt tom Expect:-header. libcurl slår på HTTP 100-continue-håndtrykket for forespørselskropper over omtrent én kilobyte, og en tidsstemplingsspørring med sertifikatforespørsel klarer vanligvis den terskelen. Noen TSA-servere svarer aldri på fortsättelsen, så klienten venter ut en full tidsavbrudd før den sender en kropp serveren ville ha akseptert umiddelbart. Å sende en tom Expect:-header undertrykker håndtrykket, og forespørselen går gjennom på én rundtur
Den andre er CURLOPT_NOSIGNAL, som må settes. Uten den implementerer libcurl tidsavbruddet for navneoppløsning med SIGALRM, og den mekanismen er ikke trådsikker. Signering kjører på en arbeidertråd, så standardatferden er en latent krasj som dukker opp under samtidighet og aldri i en test på én tråd. Å sette flagget deaktiverer den signalbaserte veien og koster bare granulariteten i resolverens tidsavbrudd
Begge defektene deler en profil som gjør dem dyre å finne senere. Ingen av dem viser seg i en funksjonell test mot en veloppdragen server på én tråd. Begge dukker opp i produksjon, mot én bestemt TSA, under last. Når du binder et nettverksbibliotek, les hva standardverdiene antar om prosessen din før du antar at de stemmer
Hvordan verifiserer du kode kompilatoren din aldri vil se?
Ved å få kompilatoren til å se det likevel, gjennom en kontrollert kopi. Utviklingsmaskinen her har ingen Linux- eller macOS-krysskompilator, så de ikke-Windows-grenene av tidsstempelenheten når aldri kodegeneratoren under et normalt bygg. Kode som aldri kompileres, er kode som råtner i stillhet: et navnebytte i en delt type, en endret parameterliste, en tillagt enhetsavhengighet, og ingen legger merke til det på måneder
Teknikken er mekanisk. Kopier enheten til en midlertidig katalog, gi den nytt navn, og erstatt hver Windows-betingelse, både {$IFDEF MSWINDOWS}-formen og {$IF DEFINED(MSWINDOWS)-formen, med et symbol som aldri er definert. Kompiler så kopien. Når alle 3 828 linjer kompilerer, har du bevist at ikke-Windows-veien bruker enheter som finnes, kaller backend-funksjoner med matchende signaturer, og refererer typer som er i virkeområde. Det er ikke bevis på at transporten fungerer, og ingenting mindre enn målplattformen gir deg det. Det er bevis på at grenen ikke allerede er knekt, noe som er feilmoden som faktisk akkumuleres
Følgevanen er å la libcurl-enheten selv være fri for plattformvakter, så den deltar i det ordinære Windows-bygget selv om ingenting der refererer til den. Dagsbygget holder da vakt over syntaksen og typene dens gratis. En enhet som bare kompilerer på en plattform du ikke har, er en enhet ingen kompilator sjekker i det hele tatt, og samme resonnement gjelder på tvers av tverrkompilatorarbeidet beskrevet i Delphi- og FPC-tverrkompilatorfellene
Å avgrense det som kommer tilbake
Et tidsstempelsvar er en liten DER-struktur, og ingenting ved transporten håndhever det. En server som er kompromittert, feilkonfigurert, eller rett og slett pekt på feil URL, kan returnere en vilkårlig strøm, og en klient som leser til tilkoblingen lukkes, vil glatt akkumulere den. Begge transporter takserer derfor svaret, noe som er det riktige stedet for grensen: å avvise på transportnivå hindrer en overdimensjonert kropp fra noen sinne å bli allokert, mens en parsersjekk først utløses etter at minnet er forpliktet
Samme resonnement gjelder URL-en. Backend-en aksepterer bare skjemaer den kan snakke meningsfullt, så en konfigurasjonsfeil feiler umiddelbart med en tydelig melding i stedet for å bli gitt til libcurl for å tolkes på hvilken som helst måte protokollstøtten tillater
Hvor transporten sitter i signeringshistorien
Tidsstempling er det første steget i historien om langsiktig validering snarere enn hele den. Tokenet må festes til signaturen, valideringsmaterialet må registreres i document security store, og arkivtidsstempler må fornyes før det gjeldende svekkes. Hele den buen er dekket i langsiktige PDF-signaturer med RFC 3161-tidsstempler og DSS
Transporten er også én brikke i en bredere portabilitetsposisjon: den native bibliotekslasteren beskrevet i å laste det native biblioteket på ethvert mål håndterer samme klasse av problem for selve PDFium-binærfilen. I begge tilfeller er mønsteret identisk: bind et lite antall symboler dynamisk, rapporter presis hva som feilet å binde, og la aldri en manglende avhengighet bli en koblingstidsfeil som stopper applikasjonen fra å starte
Windows- og ikke-Windows-tidsstempel-backend-ene følger begge med PDFium Delphi-komponent, valgt etter mål snarere enn etter konfigurasjon, så en Lazarus-applikasjon på Linux og en Delphi-applikasjon på Windows produserer samme tidsstemplede signatur gjennom ulike rørledninger