PDFium VCL zdaj na svojem zaledju OpenSSL lahko dopolni verigo podpisa PDF in po omrežju preveri preklic: kadar je OnlineRetrieval omogočen, namesti ConfigureSslCmsVerifier preverjevalnika, ki prenese manjkajoče vmesne certifikate z URL-jev caIssuers AIA in CRL-je iz točk razporeditve CRL, znotraj fiksnega proračuna časa, zahtevkov in bajtov na klic preverjanja. Preneseni certifikati so vedno le gradivo verige. Zaupanje še naprej izključno prihaja iz sistemske shrambe in sidr, ki jih nastavite
Vrzel, ki jo to zapira, se pokaže prvič, ko na strežniku Linux validirate PDF-je iz resničnega sveta. Velik del podpisnikov v CMS vgradi le svoj listni certifikat, tako da OpenSSL ne seže do korena, TrustStatus pride nazaj kot neveljaven, preklic pa se nikoli ne požene, ker veriga nikoli ni postala vredna zaupanja. Pred v3.121.0 je bilo zaledje OpenSSL, opisano v preverjanju podpisov PDF z OpenSSL v PDFium VCL, strogo brez povezave, OnlineRetrieval pa nanj ni imel učinka. Ena stvar se splača povedati vnaprej: pogon PDFium sam sploh ne opravi CMS preverjanja, tako da živi vsako spodnje pravilo v sloju PAdES komponente in njegovi vezavi OpenSSL, kjer ga lahko preberete
V kakšnem vrstem redu zaledje OpenSSL preverja, prinaša in kontrolira?
Najprej celovitost, nato zaupanje, nato preklic, omrežje pa se dotakne le med koraki, ki ga potrebujejo. VerifyCmsWithSsl preveri podpis CMS in podpisane atribute (RFC 5652) s potlačenim vrednotenjem verige, in če to spodleti, se vrne takoj, še preden seja pridobivanja sploh obstaja, tako da dokument s pokvarjenimi bajti ne sproži nobene odhajajoče zahteve. Šele, če veriga nato spodleti in je OnlineRetrieval vklopljen, sledi povezavam AIA in preveri znova. Točke razporeditve CRL se prenesejo šele, ko je verigi zaupano, ker CRL, ki visi na nezaupljivi poti, ne dokaže nič. Trije sodbi ostanejo ves čas ločeni: veljaven podpis z nepopolno verigo se še vedno poroča kot veljaven podpis
uses
PDFium, FPdfCrypto, FPdfCryptoSsl, FPdfPades;
var
Pdf: TPdf;
Probe: TPdfCmsVerifyOptions;
Diags: TPdfSslVerifyDiagnostics;
Trust: TPadesTrustValidationOptions;
Verdict: TPadesValidationResult;
I: Integer;
begin
ConfigureSslTrustAnchors(LoadCorporateRoots); // DER; edino dodatno zaupanje
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; // privzeto ptnpOffline
Trust.NetworkPolicy := ptnpOnline;
Trust.CheckRevocation := True;
Trust.UrlRetrievalTimeoutMs := 10000; // na klic preverjanja
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;
Zakaj prenesenih certifikatov nikoli ne dodajo v shrambo zaupanja?
Ker URL-ji prihajajo iz certifikata, ki se validira, in jih je izbral podpisnik. Vnos caIssuers authorityInfoAccess (RFC 5280 §4.2.2.1) je namig o tem, kje živi izdajatelj, nič več. Če bi karkoli, kar odgovori tistemu URL-ju, šlo v shrambo sidr zaupanja, bi si lahko vsakdo izdelal ključ, usmeril AIA na svoj strežnik in prejel zeleno sodbo. RetrieveIntermediates zato vsak razčlenjen certifikat poda CMS_add1_cert, ki ga postavi v nezaupljivo množico te ene strukture CMS, OpenSSL pa še vedno mora zgraditi pot od njega do sidra, ki ste ga nastavili, ali ki ga sistemska shramba že drži. Obstaja tudi tišji razlog: argument certifikata CMS_verify ni zamenjava z enakimi lastnostmi za certifikate, vgrajene v CMS, tako da je dodajanje v sam CMS zanesljiva pot
Zanka pridobivanja je namerno ozka. RetrieveIntermediates teče največ 4 krogov, vsak zbira URL-je caIssuers iz vsakega certifikata, ki je zdaj v CMS, in se ustavi, takoj ko krog ne doda nič ali je časovni proračun porabljen. Odgovor se mora razčleniti s d2i_X509 kot en sam certifikat DER, ki požre celotno telo; bajti na repu so zavrnjeni, sveženj PKCS#7 samo s certifikati, postrežen z URL-ja .p7c, pa se preskoči, namesto da bi bil razpakiran. Dostopna metoda OCSP v isti razširitvi AIA se ignorira, saj to zaledje ne govori OCSP. Na strani preklica RetrieveCrls prebere le URI-je fullName vsakega DistributionPoint (RFC 5280 §4.2.1.13) iz certifikatov CMS in nastavljenih sidr, preneseni CRL-ji pa gredo v drugo, neodvisno X509_STORE s preverjanjem CRL po celotni verigi, tako da manjkajoči ali zastarel CRL spremeni RevocationStatus, ne da bi se kadarkoli dotaknil TrustStatus
// Skrčeno iz VerifyCmsWithSsl (FPdfCryptoSsl.pas); nastavitev BIO izpuščena.
// Vsak klic _CMS_verify dobi svež BIO vsebine
if _CMS_verify(Cms, nil, nil, Bio, nil,
CMS_NO_SIGNER_CERT_VERIFY or CMS_BINARY) <> 1 then
Exit; // pokvarjen podpis: sploh brez omrežja
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, le nezaupljivo
// verigo znova preveri proti isti shrambi sidr
end;
if Options.CheckRevocation and (FetchSession <> nil) and
(Result.TrustStatus = pcvsValid) then
RetrievedCrls := RetrieveCrls(Cms, FetchSession);
// druga, ločena shramba: nastavljeni CRL-ji plus pridobljeni
Store := BuildStore(True, RetrievedCrls, RevocationChecked, CrlsMalformed);
Kaj stane en klic preverjanja največ?
Fiksen strop, vsiljen z enim samim TPdfCryptoFetchSession, ki si ga koraka AIA in CRL enega samega klica preverjanja delita. Meje so konstante v FPdfCryptoHttp, ne predlogi:
- Čas:
UrlRetrievalTimeoutMs, ki se vTPdfCmsVerifyOptions.DefaultinTPadesTrustValidationOptions.Defaultprivzeto glasi 15000; seja, ustvarjena z 0, se vrne na 30000, ura pa zažene, ko podpis prestane, in pokriva vsako poznejšo zahtevo - Zahtevki: največ 8 na sejo, preštetih, preden se transport poskusi, tako da mrtvi gostitelj še vedno porabi mesto
- Bajti: 1 MiB na odgovor in 4 MiB skupaj, URL-ji daljši od 2048 znakov pa so zavrnjeni, preden se kakršna koli povezava
Računovodstvo je strožje, kot sprva zgleda. Bajti, prejeti iz spodletelega odgovora, se še vedno štejejo v skupno vsoto, tako da strežnik, ki odgovori 404 z veliko stranjo, ne more izprazniti proračuna zastonj. Branje, ki prečka mejo na odgovor, prekine prenos, namesto da bi izsekano telo posredovalo razčlenjevalniku ASN.1, HTTP 200 s praznim telesom pa je zavrnjen na mestu, ker bi sicer pot AIA indeksirala Data[0] prazne tabele. Prestanejo le goli URL-ji http:// in https://, brez preusmeritev, piškotkov, poverilnic ali samodejnega odkrivanja posrednika, HTTPS pa obdrži svoja običajna preverjanja certifikata in imena gostitelja. Razdvojevanje URL-jev je namerno obsega enega klica: naslednja validacija mora biti zmožna videti sveže objavljen CRL. Proračun je tudi na klic, ne na dokument, ValidatePadesTrust pa preveri vsak podpis in vsak žig časa posebej, tako da najslabši primer raste s številom podpisov
Zakaj lahko zahteva WinHTTP, ki mu je potekel čas, še vedno zapiše v vaš spomin?
Ker vrnitev ob izteku časa ne prekliče klicev nazaj, ki so že v letu. Transport Windows poganja WinHTTP asinhrono in čaka na dogodek s preostalim časom seje, ko ta čakanje odneha, pa lahko zahteva še vedno zaključi branje in signalizira pozneje. Usmerite asinhrono branje na medpomnilnik sklada in ta pozni zaključek zapiše v okvir, ki takrat pripada kakšni nepovezani funkciji. Popravek je lastnina, ne odmik: dogodek in bralni medpomnilnik 16 KB živita v zapisi na kopici z dvema referencama, eno drži klicatelj, drugo pa sprosti šele zadnji klic nazaj HANDLE_CLOSING, tako da pomnilnik sprosti tista stran, ki zaključi zadnja
type
PHttpState = ^THttpState;
THttpState = record
References: LongInt; // klicatelj + zadnji klic nazaj HANDLE_CLOSING
Event: THandle;
Status, Count: DWORD;
Buffer: array[0..16383] of Byte; // tukaj pristanejo asinhrona branja, nikoli na skladu
end;
procedure ReleaseState(State: PHttpState);
begin
if InterlockedDecrement(State.References) = 0 then
begin
CloseHandle(State.Event);
Dispose(State);
end;
end;
// V klicu nazaj za status: HANDLE_CLOSING je zadnje obvestilo, ki ga WinHTTP
// pošlje za zahtevo, zato spusti drugo referenco
if Status = HttpHandleClosing then
begin
ReleaseState(State);
Exit;
end;
Kaj mora libcurl zagotoviti na FPC Unix
Asinhroni razreševalnik in gradnja, varna za niti, sicer pridobivanje po omrežju ostane izklopljeno. Na FPC Unix transport gre skozi libcurl, isto odvisnost za zaledjem žigov časa libcurl za ne-Windows cilje, vezava pa zavrne vsako knjižnico, katere maska zmožnosti nima CURL_VERSION_ASYNCHDNS ali CURL_VERSION_THREADSAFE. Razlog je, da CURLOPT_NOSIGNAL, ki ga mora nastaviti knjižnica znotraj tujega procesa, v kombinaciji s sinkronim razreševalnikom pomeni, da lahko DNS iskanje preprosto preživi iztek časa. Druga past je zaustavitev: curl_global_cleanup ne čaka asinhronih niti DNS, tako da, ko je libcurl enkrat inicializiran, modul ostane preslikan do izhoda procesa, namesto da bi nit v ozadju zažagala v raztovorjeno kodo. Ko kateri koli od pogojev spodleti, je SslCapabilities.OnlineRetrieval False in SslVerifyOptionsDiagnostics prijavi psvdOnlineRetrievalIgnored, namesto da bi se delalo, da je bilo omrežje vprašano
Kaj rezultat jamči in kaj ne
Veljaven RevocationStatus iz tega zaledja pomeni, da so bili najdeni, nastavljeni ali preneseni trenutni CRL-ji, ki pokrivajo celotno verigo, in noben ni naštel certifikata iz nje; nič več. OCSP ni, tako da CA, ki preklic objavlja le prek OCSP, pusti rezultat nepodprt, omrežna napaka pa zgleda točno tako kot CA, ki ne objavlja nič. Upoštevajte tudi, da psvdNoCrlsConfigured opisuje le CRL-je, ki ste jih nastavili, tako da je ob pridobivanju po omrežju namig, ne napoved spodletela. Kadar mora biti revizijska sled reproducijska brez dostopa do omrežja, pustite NetworkPolicy na privzeti vrednosti ptnpOffline: seja pridobivanja se ne ustvari in zaledje nikoli ne odpre povezave, kar se ujema s pogodbo brez povezave na strani CryptoAPI, opisano v preverjanjih preklica podpisov PDF brez povezave na Windows
Koda pridobivanja, proračuni in vezave transporta so del izvorne kode komponente PDFium za Delphi, tako da lahko točno potrdite, katere URL-je sme validacija obiskati in koliko sme prenesti, preden na strežniku, ki obravnava nezaupljive dokumente, omogočite ptnpOnline