PDFium VCL, CMS doğrulamasını IPdfCmsVerifier interface'inin ardında değiştirilebilir bir backend olarak ele alır; böylece PAdES doğrulayıcısı Windows'ta CryptoAPI üzerinden, macOS'ta Keychain üzerinden ve OpenSSL'in bulunduğu her yerde ConfigureSslCmsVerifier üzerinden koşabilir. Interface küçüktür. Altındaki üç OpenSSL davranışı, naif biçimde uygularsanız kendinden emin yanlış cevaplar üretir
Gerekçe, bir Delphi uygulaması Windows'tan çıktığında gayet açık hale gelir. İmza doğrulama, platform kripto yığınının bir uygulama ayrıntısı olmadığı az sayıdaki alandan biridir: hangi sertifikaların güvenildiğine, hangi algoritmaların var olduğuna ve iptalın ne anlama geldiğine o karar verir. Birisini hard-code edin ve kod portajlanmaz. Kötü soyutlayın ve her platform, çağıranın karşılaştıramayacağı farklı şekillerde cevap bildirir
Soyutlamanın gerçekte taşıması gereken şey
İki doğrulama biçimi ve üç bağımsız hüküm. Bir PDF imzası detached'tır: imzalı içerik, /Contents deliğinin iki yanındaki bayt aralıklarıdır; bu yüzden VerifyDetached tek bir tampon değil iki parça alır. Bir zaman damgası token'ı attached'tır, kendi içeriğini taşır; VerifyAttached yalnızca DER'i alır
Sonuç üç duruma ayrışır; çünkü üç farklı soruya cevap verirler ve ayrışabilirler. SignatureStatus, baytların imzalayan sertifikasındaki anahtarla imzalanıp imzalanmadığını söyler. TrustStatus, o sertifikanın güvendiğiniz bir şeye zincirlenip zincirlenmediğini söyler. RevocationStatus, sertifikanın ilgili anda hâlâ geçerli olup olmadığını söyler. Hiç duymadığınız bir sertifikadan gelen matematiksel olarak kusursuz imzalı bir belge geçerli, güvenilmeyen ve bilinmeyendir; bunu tek bir boolean'a sıkıştırmak, doğrulayıcıların kullanıcılarına yalan söyleme biçimidir
uses
FPdfCrypto, FPdfCryptoSsl;
var
Options: TPdfCmsVerifyOptions;
begin
if not SslAvailable then
raise Exception.Create('libcrypto not usable: ' + SslMissingSymbols);
ConfigureSslTrustAnchors(LoadCorporateRoots); // DER, boş olabilir
ConfigureSslCrls(LoadFreshCrls); // DER, boş olabilir
ConfigureSslCmsVerifier; // backend'i kurar
Writeln('backend : ', PadesCmsVerificationBackendName);
Writeln('library : ', SslLibraryPath, ' ', SslLibraryVersion);
Writeln('ABI : ', SslAbiLayout); // ulong=<n> long=<n>
Options := TPdfCmsVerifyOptions.Default;
Options.CheckRevocation := True;
Options.CollectChainCertificates := True;
end;
SslAbiLayout meraklı bir ayrıntı gibi görünür ve değildir. Her OpenSSL hata kodu ve her store bayrağı sınırı bir C unsigned long olarak geçer; Windows'ta dört, Linux ve macOS'ta sekiz bayttır. Sabit 32 bitlik bir tür olarak bildirin ve kod Windows'ta çalışır, sonra LP64'te yarım bir değeri sessizce okur. Varsayılan genişlikleri bir testte doğrulama kurabileceğiniz bir dizge olarak bildirmek, bütün bir platform ABI sapması sınıfını tek satırlık bir kontrole çevirir. Bir PKCS#11 bağlamasında CK_ULONG ile aynı sorunu çözmüş herkes onu anında tanır; o hikaye PKCS#11 struct packing ve CK_ULONG genişliği makalesindedir
İkinci doğrulama geçişi neden boş içerik görür?
Çünkü CMS_verify, detached içerik BIO'sunu dosya sonuna dek okur ve okunmuş bir BIO sizin için geri sarılmaz. İki geçişte doğrulamak makul bir tasarımdır: önce zincir değerlendirmesi bastırılmış hâlde yalnızca kriptografik imza, sonra tam değerlendirme; ve her iki geçiş tek bir BIO paylaşırken alışılmadık derecede aldatıcı biçimde kalır
İkinci geçiş sıfır bayt içerik alır. Detached modda bu bir hata değildir; çünkü boş bir içerik tamponu meşru bir girdidir. Özet basitçe tutmaz ve başarısızlık, içerik başarısızlığı yerine zincir kurma başarısızlığı olarak görünür; bu da sizi, asıl sorun bir akış konumu iken sertifikaları ve trust store'ları incelemeye yollar. Bellek BIO'sunu her geçiş için BIO_new_mem_buf ile yeniden kurun. Bir tahsis maliyetidir ve olasılığı tümüyle ortadan kaldırır
No-verify bayrağının bastırdıkları ve bastırmadıkları
CMS_NO_SIGNER_CERT_VERIFY, zincir değerlendirmesini bastırır, imzalayan sertifikası aramasını değil. OpenSSL içeriden, bayrağa bakmadan önce imzalayan sertifikalarını çözümler ve ekler; o bayrağı taşıyan ilk geçişten sonra imzalayan zaten hazırdır ve algoritma tanımlayıcıları hemen okunabilir. İmzalayan sertifikasını elde etmek için ikinci bir tam doğrulama koşturmaya gerek yoktur; bayrağın adının sizi varsaymaya kışkırttığı da budur
Bununla birlikte tek bir sahiplik kuralı gider. İmzalayan referansı CMS yapısına aittir ve bağımsız olarak serbest bırakılmamalıdır. Yapı yaşadığı sürece geçerlidir ve onu serbest bırakmak, belirtisi bambaşka bir yerde beliren, genellikle alakasız bir nesnenin temizliği sırasında beliren bir bozulma üretir
CRL kontrolünü açmak her imzayı neden reddettirir?
Çünkü OpenSSL, CRL'leri yalnızca store'un hâlihazırda tuttuğuyla karşılaştırır ve kendi başına hiçbir şey getirmez. CRL distribution point'lerini izlemez, OCSP de konuşmaz. İçinde hiç CRL olmayan bir store'a X509_V_FLAG_CRL_CHECK kurun ve her zincir, bir sertifika CRL'si elde edilemediği için kalır. Sonuç, iptal kontrolünün çalışıp sorun bulduğu gibi görünür. Oysa iptal kontrolü hiç çalışmamıştır
Backend bu yüzden bayrağı, yalnızca ConfigureSslCrls gerçekten en az bir CRL sağlamışken kurar. Yoksa RevocationStatus pcvsUnsupported olarak döner; bu, sorunun cevaplanmadığının dürüst ifadesidir. Aynı nedenle OnlineRetrieval bu backend'de etkisizdir ve hiçbir pcvstOnlineRetrieval checkpoint'i yayılmaz: ilerleme bildirilecek bir getirme yolu yoktur
Bu, genel olarak savunmaya değer bir tasarım duruşudur. İptal kontrolü yapamayan bir doğrulayıcı bunu söylemelidir. Denetlenmemiş bir sertifikayı iptal edilmemiş olarak bildirmek, imza doğrulama araçlarının kullanıcılarını yanlış yönlendirmenin tek en yaygın yoludur ve doğrulayıcıların PAdES imzalarını neden reddettiği makalesinde incelenen kafa karışıklığı sınıfı tam olarak budur
// Checkpoint'ler bir arayüze hangi aşamanın koştuğunu gösterir ve hangi
// aşamaları bir backend'in gerçekte yaptığını söyler
type
TSignatureProbe = class
procedure Checkpoint(Stage: TPdfCmsVerifyStage);
end;
procedure TSignatureProbe.Checkpoint(Stage: TPdfCmsVerifyStage);
begin
case Stage of
pcvstCryptographicSignature: Status('checking the signature');
pcvstChainBuild: Status('building the certificate chain');
pcvstOnlineRetrieval: Status('fetching validation data');
pcvstRevocationCheck: Status('checking revocation');
end;
end;
// Üç hükmü ayrı ayrı okuyun; ayrışmalarına izin vardır
if Result.SignatureStatus = pcvsValid then
case Result.TrustStatus of
pcvsValid: Report('signed and trusted');
pcvsInvalid: Report('signed, chain rejected');
pcvsUnsupported,
pcvsIndeterminate: Report('signed, trust not established');
end;
if Result.RevocationStatus = pcvsUnsupported then
Report('revocation was not checked on this backend');
Sabitleyemediğiniz bir kütüphaneye bağlanmak
OpenSSL, yığın erişicilerini 1.0 ile 1.1 arasında yeniden adlandırdı; aynı mantıksal işlevin, host'un elinde tesadüf eden derlemeye göre iki olası export adı vardır. Bağlama önce yeni adı çözer, eskisine düşer ve hiçbiri çözülmediğinde ancak eksik sembol kaydeder. Sevk etmediğiniz bir kütüphaneye karşı her dinamik bağlama için doğru biçim budur: güncel adları yeşle, tarihî olanlara tahammül et ve yalnızca gerçek yokluğu bildir
SslMissingSymbols, başarısız bir yüklemeyi teşhis edilebilir bir olaya çeviren şeydir. libcrypto'nun açıkça kurulu olduğu bir host'ta boş olmayan sonuç, kurulu sürümün bu derlemenin hedeflediği API'den daha eski olduğu demektir; bu, kütüphanenin hiç olmamasından bambaşka bir destek konuşmasıdır. ConfigureSslLibraryPath öteki yaygın vakayı karşılar: birkaç OpenSSL derlemesi barındıran, varsayılan arama yolundakinin istediğiniz olmadığı bir host
Platform başına backend seçmek
Pratik düzen, başlangıçta seçmek ve hangisinin cevap verdiğini kaydetmektir. Windows'ta platform backend'i, bir kurumun zaten yönettiği sertifika store'larıyla bütünleşir; genellikle istediğiniz de budur. macOS'ta Keychain backend'i aynı akla uyar ve macOS'ta SecTrust ile imza doğrulama makalesinde anlatılır. OpenSSL taşınabilir seçenektir ve her platformun trust store'unu izleyen değil, platformlar arasında özdeş bir doğrulama politikası gerektiğinde de doğru tercihtir
Hangisini kurarsanız kurun, kaydettiğiniz her hükmün yanına PadesCmsVerificationBackendName değerini yazın. Onu üreten backend'siz saklanmış bir doğrulama sonucu sonradan yeniden üretilemez; çünkü üç durum değeri, hangi yığının cevap verdiğine göre sinsi biçimde farklı anlamlar taşır. Bütün bunların üzerindeki imza inceleme katmanı, PAdES düzeylerinin nasıl bildirildiği dahil, PDF dijital imzalarını ve PAdES düzeylerini inceleme makalesinde ele alınır
Bütün bunlar, PDFium Delphi bileşeni ile kaynak olarak gelir; burada her zamankinden daha fazla önem taşır: bir imza doğrulayıcı için, bir backend'in hangi bayrakları kurduğunu ve hangi kontrolleri attığını birebir okuyabilmek lüks bir ekstra değil, uygulamadaki yeşil bir onay işaretinin gerçekte ne iddia ettiğini bilmenin tek yoludur