PDFium VCL sada može na svom OpenSSL backendu da zaokruži lanac PDF potpisa i da proveri revokaciju preko mreže: kada je OnlineRetrieval uključen, ConfigureSslCmsVerifier ugrađuje verifikator koji dovlači nedostajuće intermediate sertifikate sa AIA caIssuers URL-ova i CRL-ove sa CRL distribution points, unutar fiksnog budžeta vremena, zahteva i bajtova po pozivu verifikacije. Dovučeni sertifikati su isključivo materijal za lanac. Poverenje i dalje dolazi jedino iz sistemskog spremišta i sidara koje vi podesite
Jaz koji se ovim zatvara pokazuje se prvog trenutka kad realne PDF-ove validirate na Linux serveru. Veliki deo potpisnika u CMS ugrađuje samo svoj leaf sertifikat, pa OpenSSL ne doseže do root-a, TrustStatus se vraća kao nevalidan, i revokacija se nikada ne pokrene jer lanac nikada nije postao pouzdan. Pre v3.121.0 OpenSSL backend opisan u tekstu o verifikaciji PDF potpisa OpenSSL-om u PDFium VCL bio je strogo oflajn i OnlineRetrieval na njega nije imao uticaja. Jedno vredi reći na početku: PDFium engine sam uopšte ne radi CMS verifikaciju, pa svako pravilo ispod živi u PAdES sloju komponente i njenom OpenSSL povezivanju, gde ga možete i pročitati
Kojim redosledom OpenSSL backend verifikuje, dovlači i proverava?
Prvo integritet, pa poverenje, pa revokacija, i mreža se dodiruje samo između koraka kojima treba. VerifyCmsWithSsl proverava CMS potpis i potpisane atribute (RFC 5652) uz pritisnutu proveru lanca, i ako to padne vraća se odmah, pre nego što fetch sesija uopšte postoji, pa dokument sa pokvarenim bajtovima ne okida nijedan odlazni zahtev. Tek ako lanac potom padne a OnlineRetrieval je uključen, prati AIA veze i verifikuje ponovo. CRL distribution points dovlače se tek kad je lanac pouzdan, jer CRL koja visi sa nepouzdane putanje ništa ne dokazuje. Tri presude ostaju odvojene kroz sve: validan potpis sa nepotpunim lancem i dalje se prijavljuje kao validan potpis
uses
PDFium, FPdfCrypto, FPdfCryptoSsl, FPdfPades;
var
Pdf: TPdf;
Probe: TPdfCmsVerifyOptions;
Diags: TPdfSslVerifyDiagnostics;
Trust: TPadesTrustValidationOptions;
Verdict: TPadesValidationResult;
I: Integer;
begin
ConfigureSslTrustAnchors(LoadCorporateRoots); // DER; jedino dodatno poverenje
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 po podrazumevanju
Trust.NetworkPolicy := ptnpOnline;
Trust.CheckRevocation := True;
Trust.UrlRetrievalTimeoutMs := 10000; // po pozivu verifikacije
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;
Zašto se dovučeni sertifikati nikada ne dodaju u trust spremište?
Jer URL-ovi dolaze iz sertifikata koji se validira, a birao ih je potpisnik. authorityInfoAccess caIssuers unos (RFC 5280 §4.2.2.1) je naznaka gde stanuje izdavalac, i ništa više. Da bi u trust-anchor spremište otišlo bilo šta što odgovori na taj URL, svako bi mogao da potpiše svojim samodiranim ključem, uperi AIA u svoj server i dobije zeleni verdikt. Zato RetrieveIntermediates svaki parsiran sertifikat predaje CMS_add1_cert-u, koji ga smešta u skup nepouzdanih ove jedne CMS strukture, i OpenSSL i dalje mora od njega da izgradi putanju do sidra koje ste podesili ili koje sistemsko spremište već drži. Ima i tiši razlog: certificate argument CMS_verify-a nije zamena od štaka za sertifikate ugrađene u CMS, pa je dodavanje u sam CMS pouzdan put
Dovlačenje je namerno uzano. RetrieveIntermediates ide najviše 4 kruga, pri čemu svaki skuplja caIssuers URL-ove sa svakog sertifikata koji je tada u CMS-u, i staje čim krug ništa ne doda ili se potroši vremenski budžet. Odgovor se mora dekodovati sa d2i_X509 kao jedan DER sertifikat koji troši ceo sadržaj; bajtovi na kraju su odbijeni, i PKCS#7 certs-only paket poslužen sa .p7c URL-a preskače se umesto da se raspakuje. OCSP pristupni metod u istom AIA proširenju ignoriše se, jer ovaj backend ne govori OCSP. Na strani revokacije, RetrieveCrls čita samo fullName URI-jeve svakog DistributionPoint-a (RFC 5280 §4.2.1.13) iz CMS sertifikata i podešenih sidara, a dovučene CRL-ove sliva u drugo, nezavisno X509_STORE spremište sa CRL proverom celog lanca, pa nedostajuća ili zastarela CRL menja RevocationStatus a TrustStatus nikada ne dodiruje
// Skraćeno iz VerifyCmsWithSsl (FPdfCryptoSsl.pas); BIO priprema izostavljena.
// Svaki _CMS_verify poziv dobija svež content BIO
if _CMS_verify(Cms, nil, nil, Bio, nil,
CMS_NO_SIGNER_CERT_VERIFY or CMS_BINARY) <> 1 then
Exit; // pokvaren potpis: bez mreže u potpunosti
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, samo nepouzdano
// verifikuj lanac ponovo nad istim spremištem sidara
end;
if Options.CheckRevocation and (FetchSession <> nil) and
(Result.TrustStatus = pcvsValid) then
RetrievedCrls := RetrieveCrls(Cms, FetchSession);
// drugo, odvojeno spremište: podešene CRL-ove plus dovučene
Store := BuildStore(True, RetrievedCrls, RevocationChecked, CrlsMalformed);
Šta jedan poziv verifikacije košta najviše?
Fiksni plafon, koji sprovodi jedan TPdfCryptoFetchSession koji AIA i CRL koraci jednog poziva verifikacije dele. Granice su konstante u FPdfCryptoHttp-u, a ne sugestije:
- Vreme:
UrlRetrievalTimeoutMs, koje se uTPdfCmsVerifyOptions.DefaultiTPadesTrustValidationOptions.Defaultpodrazumeva na 15000; sesija napravljena sa 0 vraća se na 30000, a sat kreće čim potpis prođe, pokrivajući svaki kasniji zahtev - Zahtevi: najviše 8 po sesiji, brojani pre nego što se transport okuša, pa mrtav host i dalje potroši mesto
- Bajtovi: 1 MiB po odgovoru i 4 MiB ukupno, uz odbijanje URL-ova dužih od 2048 znakova pre bilo koje konekcije
Knjigovodstvo je strože nego što na prvi pogled deluje. Bajtovi primljeni iz neuspehog odgovora i dalje ulaze u ukupan zbir, pa server koji odgovori 404 velikom stranicom ne može besplatno da isprazni budžet. Čitanje koje pređe limit po odgovoru prekida preuzimanje umesto da ASN.1 parseru prosledi odsečeno telo, i HTTP 200 sa praznim telom odbija se odmah, jer bi AIA putanja u suprotnom indeksirala Data[0] praznog niza. Samo obični http:// i https:// URL-ovi prolaze, bez preusmerenja, kolačića, akreditiva i automatske pretrage proksija, dok HTTPS zadržava svoje uobičajene provere sertifikata i imena hosta. URL deduplikacija je namerno ograničena na jedan poziv: sledeća validacija mora moći da vidi sveže objavljenu CRL. Budžet je i po pozivu, ne po dokumentu, a ValidatePadesTrust verifikuje svaki potpis i svaki timestamp token odvojeno, pa najgori slučaj raste sa brojem potpisa
Zašto WinHTTP zahtev kome je isteklo vreme i dalje može da piše u vašu memoriju?
Jer povratak po isteku vremena ne otkazuje povratne pozive koji su već u letu. Windows transport pogoni WinHTTP asinhrono i čeka na događaj sa preostalim vremenom sesije, i kada to čekanje odustane, zahtev i dalje može dovršiti čitanje i javiti se kasnije. Uperite asinhrono čitanje u bafer na steku i taj kasni završetak upisaće u okvir koji tada pripada nekoj sasvim drugoj funkciji. Popravka je vlasništvo, ne tajming: događaj i bafer za čitanje od 16 KB žive u heap zapisu sa dve reference, jednu drži pozivalac a drugu otpušta samo finalni HANDLE_CLOSING povratni poziv, pa koja strana poslednja završi, ta oslobađa memoriju
type
PHttpState = ^THttpState;
THttpState = record
References: LongInt; // pozivalac + finalni HANDLE_CLOSING povratni poziv
Event: THandle;
Status, Count: DWORD;
Buffer: array[0..16383] of Byte; // asinhrona čitanja sleću ovde, nikada na stek
end;
procedure ReleaseState(State: PHttpState);
begin
if InterlockedDecrement(State.References) = 0 then
begin
CloseHandle(State.Event);
Dispose(State);
end;
end;
// U statusnom povratnom pozivu: HANDLE_CLOSING je poslednja najava koju WinHTTP
// šalje za zahtev, pa ona spušta drugu referencu
if Status = HttpHandleClosing then
begin
ReleaseState(State);
Exit;
end;
Šta libcurl mora da obezbedi na FPC Unix-u
Asinhroni rezolver i build bezbedan za niti, ili onlajn dovlačenje ostaje isključeno. Na FPC Unix-u transport ide preko libcurl-a, iste zavisnosti koja stoji iza libcurl timestamp backend-a za ne-Windows ciljeve, i povezivanje odbija svaku biblioteku čija feature maska nema ili CURL_VERSION_ASYNCHDNS ili CURL_VERSION_THREADSAFE. Razlog je što CURLOPT_NOSIGNAL, koje biblioteka u tuđem procesu mora postaviti, u paru sa sinhronim rezolverom znači da DNS upit može slobodno da preživi istek vremena. Druga zamka je gašenje: curl_global_cleanup ne čeka asinhrona DNS niti, pa jednom inicijalizovan libcurl ostaje mapiran dok proces ne izađe, umesto da pozadinska nit otrči u istovareni kod. Kad bilo koji od ta dva uslova padne, SslCapabilities.OnlineRetrieval je False a SslVerifyOptionsDiagnostics prijavljuje psvdOnlineRetrievalIgnored umesto da se pravi da je mreža bila pitana
Šta rezultat garantuje, a šta ne
Validan RevocationStatus sa ovog backenda znači da su pronađene, podešene ili dovučene tekuće CRL-ove koje pokrivaju ceo lanac i da nijedna nije upisala sertifikat iz njega; i ništa više. OCSP nema, pa CA koja revokaciju objavljuje samo kroz OCSP ostavlja rezultat nepodržanim, i mrežni kvar izgleda potpuno isto kao CA koja ništa ne objavljuje. Primetite i da psvdNoCrlsConfigured opisuje samo CRL-ove koje ste vi podesili, pa je uz onlajn dovlačenje to naznaka, a ne prognoza pada. Kad audit trag mora biti reprodukovan bez pristupa mreži, ostavite NetworkPolicy na podrazumevanom ptnpOffline-u: fetch sesija se ne pravi i backend nikada ne otvara konekciju, što se poklapa sa oflajn ugovorom na CryptoAPI strani opisanim u tekstu o oflajn proverama revokacije PDF potpisa na Windows-u
Dovlačenje, budžeti i transport povezivanja stižu kao source uz PDFium Delphi komponentu, pa možete tačno da potvrdite koje URL-ove validacija sme da kontaktira i koliko sme da preuzme pre nego što uključite ptnpOnline na serveru koji rukuje nepouzdanim dokumentima