PDFium VCL, PDF imza iptalini Windows'ta offline kontrol eder; CertGetCertificateChain'in iptal geçişine CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY ekleyerek, çünkü zincir kurma için kullanılan cache-only bayrağı CRL ya da OCSP çekimini hiç kapsamaz. v3.119.1'den beri offline bir ValidatePadesTrust çağrısı ağdan uzak kalır ve temiz bir sonuç, sertifika başına gerçek iptal kanıtı ister. Bu yazının geri kalanı, o cümlenin iki yarısının da neden düzeltilmesi gerektiğiyle ilgili
Sorunu açığa çıkaran düzen sıradandır. Bir doğrulama servisi kilitli bir Windows host'ta çalışır, TPadesTrustValidationOptions.NetworkPolicy ptnpOffline'dir (aynı zamanda varsaylandır) ve operatör her cevabın yerel sertifika önbelleğinden gelmesini bekler. Sonra biri güvenlik duvarı günlüğünde bir CA dağıtım noktasına giden istekleri fark eder ya da her imzada UrlRetrievalTimeoutMs'in tamamı olan 15000 ms boyunca takılan bir toplu iş görür. Kod, ağ isteyen hiçbir şey içermiyordu. Windows yine de oraya gitti
Offline bir zincir kurulumu Windows'ta hâlâ neden CRL çekiyor?
Çünkü CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL yalnızca zincir kurmanın yaptığı URL çekimini kısıtlar: AIA issuer çekimleri, kök ve CTL güncellemeleri. CertGetCertificateChain için Microsoft dokümantasyonu, bayrağın iptal kontrolüne uygulanmadığını açıkça söyler. İptalin kendi anahtarı vardır, CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY ($80000000); o olmadan iptal sağlayıcıları, çevresindeki çağrı offline görünse bile bir CRL indirmekte ya da OCSP isteği göndermekte serbesttir. PDFium VCL artık OnlineRetrieval False olduğunda o bayrağı, zincir bayraklarına, CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT ve CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT'un üzerine iptal geçişine OR'lar. Bu, gecikmeden fazlasını ilgilendirir: bir OCSP isteği, responder'a hangi sertifikaya baktığınızı söyler; hava boşluklu bir doğrulayıcının kaçınması gereken şey tam olarak budur
uses
PDFium, FPdfCrypto, FPdfPades;
var
Options: TPadesTrustValidationOptions;
Report: TPadesValidationResult;
begin
Options := TPadesTrustValidationOptions.Default; // ptnpOffline, 15000 ms
Options.CheckRevocation := True; // varsayılan False
Options.CheckTimeStamps := True;
// Offline artık iptal için de offline demek: yalnızca önbellekteki CRL ve OCSP
// yanıtları; pcvstOnlineRetrieval checkpoint'i yükseltilmez
Report := Pdf.ValidatePadesTrust(Options);
end;
İki zincir kurulumu, iki hata alanı
Windows backend zinciri iki kez kurar ve her kurulum artık kendi hata yuvasına sahiptir. İlk CertGetCertificateChain çağrısı iptal bayrakları olmadan çalışır ve CertVerifyCertificateChainPolicy'yi temel politika ile besler; bu TrustStatus ve TrustError üretir. İkinci çağrı iptal bayraklarını ekler. v3.119.1 öncesinde o ikinci çağrının başarısızlığı GetLastError'ı TrustError'a yazıyordu; henüz güvenilir doğrulanmış bir zincir, bir iptal sağlayıcısı takıldığı için güvenilmez görünebiliyordu. Düzeltme GetLastError'ı hemen okur ve TPdfCmsVerifyResult.RevocationError'a saklar, ilk geçişin hükmüne dokunmaz. Ve ikinci çağrıdan dönen bir True da başarı sayılmaz; yalnızca Windows'un incelemeye değer bir zincir bağlamı geri verdiğini ifade eder
Sıfır bir trust error maskesi gerçekte neyi kanıtlar?
Tek başına hiçbir şey. İptal geçişinden sonra sıfır olan toplam TrustStatus.dwErrorStatus, hiçbir hata bitinin kaldırılmadığını söyler ve hiçbir elementin hiç iptal bilgisi taşımadığı bir zincir tam olarak bunu üretebilir. Daha eski kod, "iptal biti yok, bilinmeyen biti yok, offline biti yok"u doğrudan geçerliye eşliyordu; bir doğrulayıcının kontrol edilmemiş bir sertifikayı temiz olarak raporlamasının klasik yolu budur. Yeni ReadWinRevocationEvidence rutini her basit zinciri ve her elementi gezer, cbSize'u güvenle okumak için fazla küçük olan yapıları reddeder ve en az bir kök-olmayan element mevcutken ve bu elementlerin her biri dwRevocationResult değeri sıfır olan bir CERT_REVOCATION_INFO taşırken başarı raporlar
// Kanıt gezintisinden yoğunlaştırıldı: bir element yalnızca onun için
// bir iptal sağlayıcısı gerçekte cevap verdiğinde sayılır
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); // yalnızca kökten oluşan zincir hiçbir şey kanıtlamaz
Provider sonucu ham tutulur. RevocationError, dwRevocationResult DWORD'unu provider'ın döndürdüğü gibi taşır; iptal edilmiş bir element varken onun hatasını tercih eder (CRYPT_E_REVOKED, $80092010'dır) ve trust bitmaskesi asla yerli bir hata kodu kılığına sokulmaz. TPdfCmsRevocationReason eşlemesi bilinçli olarak kabadır: açık iptal için pcvsInvalid ile pcrrCertificateRevoked, zincir iptalle ilgisi olmayan nedenlerle düşmüşse pcrrChainUntrusted, geri kalan her şey için pcrrUnknown. Windows OCSP denemiş olabilir, CRL değil; offline ya da bilinmeyen bir sonuç pcrrCrlExpired'e çevrilmez. OpenSSL CMS doğrulama backend'i o CRL'ye-özgü ayrımları yapabilir, çünkü yalnızca ona verdiğiniz CRL'leri değerlendirir; macOS SecTrust backend'i ise alanları pcrrNone ve sıfırda bırakır — bu "ayrıntılı tanı yok" demektir, "geçti" değil
Kök hariç tutmasının nerede durduğu
CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT, çapayı meşru biçimde atlar; kimse bir kökü kendisine karşı iptal eden bir CRL yayımlamaz. Tuzak, hangi elementin kök olduğuna karar vermektir. PDFium VCL basit bir zincirin son elementini, dwInfoStatus'u onu self-signed ($00000008) ya da açıkça CA-trusted ($00004000) işaretlediğinde hariç tutar. Offline bir host eksik bir issuer'i çoğu zaman çekemez, zincir bir intermediate'da biter; o kısmi zincirin son elementini kök saymak, iptal durumu önbellekte en çok bulunmayacak olan sertifikayı sessizce düşürmek olurdu. O element gerekliler kümesinde kalır, provider cevabı yoktur ve sonuç pcvsIndeterminate kalır
İmza ve timestamp iptal sonuçları nasıl ayrı tutuluyor?
Birbirini asla üzerine yazmayan ayrı alanlar olarak. PAdES doğrulayıcısı, belge imzasının detached CMS'ini ve RFC 3161 timestamp token'ının attached CMS'ini iki bağımsız çağrıda doğrular ve v3.119.0 her birine TPadesSignatureValidation üzerinde kendi tanılarını verdi: imzalayıcı için RevocationReason ve NativeRevocationError, TSA için TimeStampRevocationReason ve NativeTimeStampRevocationError. İptal edilmiş bir TSA sertifikası bu yüzden iptal edilmiş bir imzalayıcı kılığına giremez ve bir timestamp başarısızlığı, hâlihazırda kurulmuş bir bütünlük sonucunu silmez. CheckRevocation False olduğunda ya da doğrulama o aşamaya hiç ulaşmadığında alanlar pcrrNone ve 0 kalır; onları her zaman RevocationStatus ve TimeStampRevocationStatus'un yanında okuyun
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;
Kanıt raporu aynı kuralı izler. CSV dışa aktarımı revocationReason, nativeRevocationError ile timestamp sütunlarını nativeTimeStampRevocationError'a dek, mevcut sütun sırasının sonuna ekler; eski parser'lar çalışmaya devam eder ve JSON dışa aktarımı eski alanların anlamını değiştirmeden karşılık gelen alanları ekler. Offline doğrulama belirsiz dönmeye devam ediyorsa kalıcı çözüm upstream'tedir: doğrulama malzemesini imza anında toplayın — RFC 3161 timestamp'leri ve DSS ile uzun süreli PDF imzaları yazısında anlatıldığı gibi — doğrulayan makinenin sıcak bir önbelleği olmasını ummaktansa
Test matrisinin kanıtladıkları ve kanıtlamadıkları
Windows doğrulama matrisi, her Delphi ve FPC Win32 ile Win64 hedefinde 30 kontrollü chain-API senaryosunu ve bir gerçek offline CMS duman testini geçti. Gerçek duman testi, güvenilmeyen özel bir CA altındaki geçerli bir imzayı doğrular; temiz ve açıkça iptal edilmiş sonuçlar kurulu trust anchor'lardan ya da canlı çekimden değil, stub edilmiş CertGetCertificateChain yanıtlarından gelir. Dürüstçe belirtilmeye değer bir sınır bu: bayrak işleme, hata izolasyonu ve kanıt gezintisi çakılı durumda; belli bir makinenin iptal önbelleğinin belli bir günde ne içerdiği hâlâ Windows'un işidir ve boş bir önbellek artık bir ağ isteği ya da sahte bir "geçerli" yerine doğru biçimde "bilinmeyen" üretir
Offline iptal işleme, alan-başı tanılar ve kanıt dışa aktarımları, Delphi ve C++Builder için PDFium VCL içindeki PDF imza doğrulama API'sinin bir parçasıdır; çapraz-platform dağıtımlar için OpenSSL ve macOS backend'leri de yanındadır