PDFium VCL kan nå fullføre en PDF-signaturkjede og sjekke tilbakekalling over nettverket på OpenSSL-backend-en sin: når OnlineRetrieval er slått på, installerer ConfigureSslCmsVerifier en verifikator som laster ned manglende mellomsertifikater fra AIA caIssuers-URL-ene og CRL-er fra CRL-distribusjonspunktene, innenfor et fast tids-, forespørsels- og bytebudsjett per verifiseringskall. Nedlastede sertifikater er aldri noe annet enn kjedemateriale. Tillit kommer fortsatt utelukkende fra systembutikken og ankerne du konfigurerer
Gapet dette lukker, viser seg første gang du validerer ekte PDF-er på en Linux-server. En stor andel av signatarer legger bare inn sitt eget leaf-sertifikat i CMS-en, så OpenSSL når aldri en rot, TrustStatus kommer tilbake ugyldig, og tilbakekalling kjører aldri fordi kjeden aldri ble tiltrodd. Før v3.121.0 var OpenSSL-backend-en beskrevet i å verifisere PDF-signaturer med OpenSSL i PDFium VCL strengt offline og OnlineRetrieval hadde ingen virkning på den. Én ting er verdt å si oppfront: selve PDFium-motoren gjør ingen CMS-verifisering i det hele tatt, så hver regel under bor i komponentens PAdES-lag og dets OpenSSL-binding, der du kan lese den
I hvilken rekkefølge verifiserer, henter og sjekker OpenSSL-backend-en?
Integritet først, så tillit, så tilbakekalling, og nettverket berøres bare mellom trinnene som trenger det. VerifyCmsWithSsl sjekker CMS-signaturen og signed attributes (RFC 5652) med kjedeevaluering undertrykt, og hvis det feiler, returnerer den umiddelbart, før en fetch-økt i det hele tatt finnes, så et dokument med ødelagte byte utløser ingen utgående forespørsel. Bare hvis kjeden så feiler og OnlineRetrieval er på, følger den AIA-lenker og verifiserer igjen. CRL-distribusjonspunkter hentes bare når kjeden er tiltrodd, for en CRL som henger på en utrustet vei beviser ingenting. De tre dommene holder seg atskilt hele veien: en gyldig signatur med en ufullstendig kjede rapporteres fortsatt som en gyldig signatur
uses
PDFium, FPdfCrypto, FPdfCryptoSsl, FPdfPades;
var
Pdf: TPdf;
Probe: TPdfCmsVerifyOptions;
Diags: TPdfSslVerifyDiagnostics;
Trust: TPadesTrustValidationOptions;
Verdict: TPadesValidationResult;
I: Integer;
begin
ConfigureSslTrustAnchors(LoadCorporateRoots); // DER; den eneste ekstra tilliten
ConfigureSslCmsVerifier;
Probe := TPdfCmsVerifyOptions.Default;
Probe.OnlineRetrieval := True;
Probe.CheckRevocation := True;
Diags := SslVerifyOptionsDiagnostics(Probe);
if psvdOnlineRetrievalIgnored in Diags then
Log('no HTTP transport or CMS_add1_cert: validation stays offline');
Trust := TPadesTrustValidationOptions.Default; // ptnpOffline som standard
Trust.NetworkPolicy := ptnpOnline;
Trust.CheckRevocation := True;
Trust.UrlRetrievalTimeoutMs := 10000; // per verifiseringskall
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'signed-contract.pdf';
Pdf.Active := True;
Verdict := Pdf.ValidatePadesTrust(Trust);
for I := 0 to High(Verdict.Signatures) do
Log(Format('#%d trust=%d revocation=%d', [I,
Ord(Verdict.Signatures[I].CertificateTrustStatus),
Ord(Verdict.Signatures[I].RevocationStatus)]));
finally
Pdf.Free;
end;
end;
Hvorfor blir nedlastede sertifikater aldri lagt til i trust-butikken?
For URL-ene kommer fra sertifikatet som valideres, og signataren valgte dem. authorityInfoAccess caIssuers-oppføringen (RFC 5280 §4.2.2.1) er et hint om der utstederen bor, ingenting mer. Gikk hva enn som svarer på den URL-en inn i trust-anchor-butikken, kunne hvem som helst signere med en selvlaget nøkkel, peke AIA mot sin egen server og motta en grønn dom. RetrieveIntermediates sender derfor hvert parsede sertifikat til CMS_add1_cert, som plasserer det i untrusted-settet til denne ene CMS-strukturen, og OpenSSL må fortsatt bygge en vei fra det til et anker du har konfigurert eller som systembutikken allerede holder. Det finnes også en stillere grunn: sertifikatargumentet til CMS_verify er ikke en drop-in-erstatning for sertifikatene innebygd i CMS-en, så å legge til i selve CMS-en er den pålitelige veien
Hentingsløkken er bevisst smal. RetrieveIntermediates kjører høyst 4 runder, der hver samler caIssuers-URL-er fra hvert sertifikat som nå er i CMS-en, og stopper så snart en runde ikke legger til noe eller tidsbudsjettet er brukt opp. Et svar må dekode med d2i_X509 som et enkelt DER-sertifikat som konsumerer hele body-en; etterfølgende byte avvises, og en PKCS#7 certs-only-bundle servert fra en .p7c-URL hoppes over i stedet for å pakkes ut. OCSP-tilgangsmetoden i samme AIA-utvidelse ignoreres, siden denne backend-en ikke snakker OCSP. På tilbakekallingssiden leser RetrieveCrls bare fullName-URI-ene til hver DistributionPoint (RFC 5280 §4.2.1.13) fra CMS-sertifikatene og de konfigurerte ankerne, og de nedlastede CRL-ene går inn i en andre, uavhengig X509_STORE med fullkjede CRL-sjekking, så en manglende eller foreldet CRL endrer RevocationStatus uten noensinne å røre TrustStatus
// Komprimert fra VerifyCmsWithSsl (FPdfCryptoSsl.pas); BIO-oppsett utelatt.
// Hvert _CMS_verify-kall får en fersk content BIO
if _CMS_verify(Cms, nil, nil, Bio, nil,
CMS_NO_SIGNER_CERT_VERIFY or CMS_BINARY) <> 1 then
Exit; // ødelagt signatur: intet nettverk i det hele tatt
if Options.OnlineRetrieval and SslCapabilities.OnlineRetrieval then
FetchSession := TPdfCryptoFetchSession.Create(Options.UrlRetrievalTimeoutMs);
Store := BuildStore(False, nil, RevocationChecked, CrlsMalformed);
if (_CMS_verify(Cms, nil, Store, Bio, nil, CMS_BINARY) <> 1) and
(FetchSession <> nil) then
begin
RetrieveIntermediates(Cms, FetchSession); // CMS_add1_cert, bare untrusted
// verifiser kjeden igjen mot samme ankerbutikk
end;
if Options.CheckRevocation and (FetchSession <> nil) and
(Result.TrustStatus = pcvsValid) then
RetrievedCrls := RetrieveCrls(Cms, FetchSession);
// andre, egen butikk: konfigurerte CRL-er pluss hentede
Store := BuildStore(True, RetrievedCrls, RevocationChecked, CrlsMalformed);
Hva koster ett verifiseringskall maksimalt?
Et fast tak, håndhevet av én TPdfCryptoFetchSession som AIA- og CRL-trinnene i ett enkelt verifiseringskall deler. Grensene er konstanter i FPdfCryptoHttp, ikke forslag:
- Tid:
UrlRetrievalTimeoutMs, som står til 15000 som standard i bådeTPdfCmsVerifyOptions.DefaultogTPadesTrustValidationOptions.Default; en økt laget med 0 faller tilbake på 30000, og klokken starter så snart signaturen har passert, og dekker enhver senere forespørsel - Forespørsler: høyst 8 per økt, talt før transporten forsøkes, så en død vert bruker fortsatt opp en plass
- Byte: 1 MiB per respons og 4 MiB totalt, med URL-er lengre enn 2048 tegn avvist før noen tilkobling
Bokføringen er strengere enn den først ser ut. Byte mottatt fra en feilet respons teller fortsatt mot totalen, så en server som svarer 404 med en stor side, kan ikke tappe budsjettet gratis. Lesingen som krysser respons-grensen, avbryter nedlastingen i stedet for å sende en avkortet body videre til ASN.1-parseren, og en HTTP 200 med tom body avvises kontant, for AIA-veien ville ellers indeksert Data[0] i en tom array. Bare rene http:// og https://-URL-er slipper gjennom, uten redirecter, informasjonskapsler, legitimasjon eller automatisk proxy-oppdagelse, mens HTTPS beholder sine vanlige sertifikat- og vertsnavnsjekker. URL-deduplisering er med vilje avgrenset til ett kall: neste validering må kunne se en fersk publisert CRL. Budsjettet er også per kall, ikke per dokument, og ValidatePadesTrust verifiserer hver signatur og hvert tidsstempel-token hver for seg, så verste tilfellet vokser med antall signaturer
Hvorfor kan en tidsavbrutt WinHTTP-forespørsel fortsatt skrive inn i minnet ditt?
For å returnere ved tidsavbrudd kansellerer ikke callback-ene allerede i luften. Windows-transporten driver WinHTTP asynkront og venter på et event med øktens gjenværende tid, og når det ventet gir seg, kan forespørselen fortsatt fullføre en lesing og signalere etterpå. Peker du den asynkrone lesingen mot en stack-buffer, skriver den sene fullføringen inn i et ramme-område som da tilhører en helt annen funksjon. Fiksen er eierskap, ikke timing: event-en og 16 KB lesebufferen bor i en heap-record med to referanser, én holdt av kalleren og én sluppet bare av den siste HANDLE_CLOSING-callback-en, så uansett hvilken side som blir ferdig sist, frigjør den minnet
type
PHttpState = ^THttpState;
THttpState = record
References: LongInt; // kaller + siste HANDLE_CLOSING-callback
Event: THandle;
Status, Count: DWORD;
Buffer: array[0..16383] of Byte; // async-lesinger lander her, aldri på en stack
end;
procedure ReleaseState(State: PHttpState);
begin
if InterlockedDecrement(State.References) = 0 then
begin
CloseHandle(State.Event);
Dispose(State);
end;
end;
// I status-callbacken: HANDLE_CLOSING er siste varsling WinHTTP
// sender for forespørselen, så den slipper den andre referansen
if Status = HttpHandleClosing then
begin
ReleaseState(State);
Exit;
end;
Hva libcurl må tilby på FPC Unix
En asynkron resolver og et trådsikkert bygg, ellers forblir netthenting av. På FPC Unix går transporten gjennom libcurl, samme avhengighet bak libcurl timestamp-backend for ikke-Windows-mål, og bindingen nekter ethvert bibliotek hvis feature-maske mangler enten CURL_VERSION_ASYNCHDNS eller CURL_VERSION_THREADSAFE. Grunnen er at CURLOPT_NOSIGNAL, som et bibliotek inne i noens annen prosess må sette, kombinert med en synkron resolver betyr at et DNS-oppslag ganske enkelt kan overleve tidsavbruddet. Den andre fellen er nedstenging: curl_global_cleanup venter ikke på asynkrone DNS-tråder, så når libcurl først er initialisert, forblir modulen mappet til prosessen avslutter, i stedet for å la en bakgrunnstråd løpe inn i avlastet kode. Feiler ett av kravene, er SslCapabilities.OnlineRetrieval False og SslVerifyOptionsDiagnostics rapporterer psvdOnlineRetrievalIgnored i stedet for å late som nettverket ble konsultert
Hva resultatet garanterer og ikke garanterer
En gyldig RevocationStatus fra denne backend-en betyr at aktuelle CRL-er som dekker hele kjeden ble funnet, konfigurert eller lastet ned, og ingen listet opp et sertifikat i den; ingenting mer. Det finnes ingen OCSP, så en CA som publiserer tilbakekalling bare gjennom OCSP, etterlater resultatet ustøttet, og en nettverksfeil ser nøyaktig ut som en CA som ikke publiserer noe. Merk også at psvdNoCrlsConfigured beskriver bare CRL-ene du konfigurerte, så med netthenting er det et hint, ikke en spådom om feil. Når et revisjonsspor må være reproduserbart uten nettverkstilgang, la NetworkPolicy stå på ptnpOffline-standardverdien sin: ingen fetch-økt opprettes og backend-en åpner aldri en tilkobling, noe som matcher den offline-kontrakten på CryptoAPI-siden beskrevet i offline PDF-signatur tilbakekallingssjekker på Windows
Hentingskoden, budsjettene og transportbindingene følger med som kildekode med PDFium Delphi-komponenten, så du kan bekrefte nøyaktig hvilke URL-er en validering kan kontakte og hvor mye den kan laste ned, før du slår på ptnpOnline på en server som håndterer utrustede dokumenter