PDFium VCL preverja preklice podpisov PDF brez povezave na Windows tako, da k preklicnemu prehodu CertGetCertificateChain doda CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY, ker zastavica samo-iz-predpomnilnika, uporabljena pri gradnji verige, pridobivanja CRL ali OCSP sploh ne pokriva. Od v3.119.1 offline klic ValidatePadesTrust ostane izven omrežja in čist rezultat zahteva dejanske dokaze o preklicu na potrdilo. Preostanek te objave je o tem, zakaj je bilo treba popraviti obe polovici te povedi
Nastavitev, ki razkrije težavo, je vsakdanja. Validacijska storitev teče na zaklenjenem gostitelju Windows, TPadesTrustValidationOptions.NetworkPolicy je ptnpOffline (kar je tudi privzeto), operater pa pričakuje, da bo vsak odgovor prišel iz lokalnega predpomnilnika potrdil. Potem nekdo opazi odhodne zahteve k distribucijski točki CA v dnevniku požarnega zidu ali paketno opravilo, ki se zatakne za polne UrlRetrievalTimeoutMs 15.000 ms pri vsakem podpisu. Nič v kodi ni prosilo za omrežje. Windows je šel tja vseeno
Zakaj offline gradnja verige še vedno pridobi CRL na Windows?
Ker CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL omeji le pridobivanje URL, ki ga dela gradnja verige: pridobivanja izdajateljev AIA, posodobitve korenov in CTL. Microsoftova dokumentacija za CertGetCertificateChain izrecno pove, da zastavica ne velja za preverjanje preklicov. Preklic ima svoje stikalo, CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY ($80000000), in brez njega so ponudniki preklicov svobodni, da prenesejo CRL ali pošljejo zahtevo OCSP, čeprav obkrožajoči klic izgleda brez povezave. PDFium VCL zdaj to zastavico z OR združi s preklicnim prehodom, kadarkoli je OnlineRetrieval False, na vrhu zastavic verige, CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT in CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT. To šteje za več kot zakasnitev: zahteva OCSP pove odgovorjalcu, katero potrdilo gledate — točno to, česar se mora izogibati validator, odrezan od omrežja
uses
PDFium, FPdfCrypto, FPdfPades;
var
Options: TPadesTrustValidationOptions;
Report: TPadesValidationResult;
begin
Options := TPadesTrustValidationOptions.Default; // ptnpOffline, 15000 ms
Options.CheckRevocation := True; // privzeto False
Options.CheckTimeStamps := True;
// Offline zdaj pomeni offline tudi za preklic: le predpomnjeni CRL in OCSP
// odgovori, kontrolna točka pcvstOnlineRetrieval se ne sproži
Report := Pdf.ValidatePadesTrust(Options);
end;
Dve gradnji verige, dve polji napak
Zaledna stran Windows gradi verigo dvakrat in vsaka gradnja zdaj lasti svojo režo napake. Prvi klic CertGetCertificateChain teče brez zastavic preklica in CertVerifyCertificateChainPolicy napaja z osnovno politiko, kar proizvede TrustStatus in TrustError. Drugi klic doda zastavice preklica. Pred v3.119.1 je odpoved drugega klica zapisala GetLastError v TrustError, tako da je lahko veriga, pravkar potrjena kot zaupanja vredna, prišla nazaj videti nezaupanja vredna, ker je kihnil ponudnik preklicov. Popravek takoj prebere GetLastError in ga shrani v TPdfCmsVerifyResult.RevocationError, sodbo prvega prehoda pa pusti pri miru. In True, vrnjeno iz drugega klica, se ne obravnava kot uspeh; pomeni le, da je Windows vrnil kontekst verige, vreden pregleda
Kaj dejansko dokaže ničelna maska napak zaupanja?
Sam zase, nič. Združeni TrustStatus.dwErrorStatus nič po preklicnem prehodu pove, da ni bil dvignjen noben bit napake, veriga, kjer noben element sploh ni nosil informacije o preklicu, pa lahko proizvede točno to. Starejša koda je preslikala »noben bit preklica, noben neznan bit, noben offline bit« naravnost v veljavno — klasičen način, kako validator prijavi nepreverjeno potrdilo kot čisto. Nova rutina ReadWinRevocationEvidence obhodi vsako preprosto verigo in vsak element, zavrne strukture, katerih cbSize je premajhen za varno branje, in prijavi uspeh le, kadar obstaja vsaj en element, ki ni koren, in vsak tak element nosi CERT_REVOCATION_INFO, katerega dwRevocationResult je nič
// Skrčeno iz obhoda dokazov: element šteje le, kadar je zanj
// dejansko odgovoril ponudnik preklicov
for J := 0 to ElementCount - 1 do
begin
Element := Elements[J];
ExcludedRoot := (J = ElementCount - 1) and
((Element^.TrustStatus.dwInfoStatus and
(CERT_TRUST_IS_SELF_SIGNED or CERT_TRUST_IS_CA_TRUSTED)) <> 0);
InfoPresent := (Element^.pRevocationInfo <> nil) and
(Element^.pRevocationInfo^.cbSize >= SizeOf(TCERT_REVOCATION_INFO));
if not ExcludedRoot then
begin
Inc(RequiredCount);
if not InfoPresent or
(Element^.pRevocationInfo^.dwRevocationResult <> 0) then
Complete := False;
end;
end;
Complete := Complete and (RequiredCount > 0); // veriga samo s korenom ne dokaže nič
Rezultat ponudnika ostane surov. RevocationError nosi DWORD dwRevocationResult točno tako, kot ga je vrnil ponudnik, pri čemer raje vzame napako preklicanega elementa, kadar ta obstaja (CRYPT_E_REVOKED je $80092010), in bitna maska zaupanja se nikoli ne obleče v domačo kodo napake. Preslikava v TPdfCmsRevocationReason je namerno groba: pcrrCertificateRevoked z pcvsInvalid za izrecen preklic, pcrrChainUntrusted, kadar veriga spodleti iz razlogov, nesorodnih preklicu, in pcrrUnknown za vse ostalo. Windows je lahko poskusil OCSP namesto CRL, zato se offline ali neznan rezultat ne prevede v pcrrCrlExpired. Zaledje za overjanje CMS OpenSSL lahko naredi razlike, specifične za CRL, ker ovrednoti le CRL, ki mu jih podate, zaledje SecTrust za macOS pa polja pusti na pcrrNone in nič, kar pomeni »brez podrobne diagnostike« in ne »uspešno«
Kje se izključitev korena ustavi
CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT upravičeno preskoči sidro, ker nihče ne objavlja CRL, ki bi preklical koren proti njemu samemu. Past je odločiti, kateri element je koren. PDFium VCL izključi zadnji element preproste verige le, kadar ga njegov dwInfoStatus označi kot samopodpisan ($00000008) ali izrecno CA-zaupan ($00004000). Gostitelj brez povezave pogosto ne more pridobiti manjkajočega izdajatelja, zato se veriga konča pri vmesnem; obravnavati zadnji element te delne verige kot koren bi tiho odvrglo tisto potrdilo, katerega status preklica je najbolj verjetno odsoten iz predpomnilnika. Ta element ostane v zahtevani množici, nima odgovora ponudnika in rezultat ostane pcvsIndeterminate
Kako se rezultati preklica podpisa in časovnega žiga držita ločeno?
Kot ločena polja, ki se nikoli ne prepišeta. Validator PAdES potrdi ločeno CMS podpisa dokumenta in priloženo CMS žetona časovnega žiga RFC 3161 v dveh neodvisnih klicih, v3.119.0 pa je vsakemu dal njegovo lastno diagnostiko na TPadesSignatureValidation: RevocationReason in NativeRevocationError za podpisovalca, TimeStampRevocationReason in NativeTimeStampRevocationError za TSA. Preklicano potrdilo TSA zato ne more nastopati kot preklican podpisovalec, odpoved časovnega žiga pa ne izbriše rezultata celovitosti, ki je že bil ugotovljen. Ko je CheckRevocation False ali validacija do te faze sploh ni prišla, polja ostanejo pcrrNone in 0, zato ju vedno berite poleg RevocationStatus in TimeStampRevocationStatus
for I := 0 to High(Report.Signatures) do
begin
S := Report.Signatures[I];
case S.RevocationStatus of
pcsInvalid:
Log(Format('sig %d: signer revoked, provider 0x%.8x',
[I, S.NativeRevocationError]));
pcsIndeterminate:
Log(Format('sig %d: revocation unknown, reason %d, provider 0x%.8x',
[I, Ord(S.RevocationReason), S.NativeRevocationError]));
pcsNotChecked:
Log(Format('sig %d: revocation not checked', [I]));
end;
if S.TimeStampRevocationStatus = pcsIndeterminate then
Log(Format('sig %d: TSA revocation unknown, provider 0x%.8x',
[I, S.NativeTimeStampRevocationError]));
end;
Poročilo dokazov sledi istemu pravilu. Izvoz CSV pripne revocationReason, nativeRevocationError in stolpce časovnega žiga do nativeTimeStampRevocationError na konec obstoječega vrstnega reda stolpcev, tako da starejši razčlenjevalniki še naprej delujejo, izvoz JSON pa doda ujemajoča se polja, ne da bi spremenil pomen starih. Če validacija brez povezave vztrajno prihaja nazaj kot nedoločena, je trajen popravek navzgor: zberite validacijski material ob podpisovanju, kot opisuje dolgoročni podpisi PDF s časovnimi žigi RFC 3161 in DSS, namesto da bi upali, da ima preverjalni stroj topel predpomnilnik
Kaj matrika preizkusov dokaže in česa ne
Matrika overjanja Windows je prestala 30 nadzorovanih scenarijev API-ja verig in en pravi offline dimni preizkus CMS na vsakem cilju Delphi in FPC Win32 ter Win64. Pravi dimni preizkus potrdi veljaven podpis pod nezaupanja vrednim zasebnim CA, medtem ko čiste in izrecno preklicane izide prinašajo zastavljeni (stub) odgovori CertGetCertificateChain in ne nameščena sidra zaupanja ali živo pridobivanje. To je iskrena meja, vredna izrekanja: ravnanje z zastavicami, izolacija napak in obhod dokazov so pripeti, kaj pa vsebuje predpomnilnik preklicov posameznega stroja na dani dan, je še vedno zadeva Windows, prazen predpomnilnik pa zdaj pravilno proizvede »neznano« namesto omrežne zahteve ali lažnega »veljavno«
Ravnanje s preklici brez povezave, diagnostika na polje in izvozi dokazov so del API-ja za validacijo podpisov PDF v PDFium VCL za Delphi in C++Builder, skupaj z zaledji OpenSSL in macOS za večplatformske namestitve