Teknik Makale

PDFium VCL'de OpenSSL ile PDF İmzalarını Doğrulama

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

Üç tuzağın PDFium VCL OpenSSL CMS doğrulayıcı şeması: dosya sonuna dek okunan paylaşımlı içerik BIOsu ikinci doğrulama geçişini sıfır baytla bırakır, CMS_NO_SIGNER_CERT_VERIFY zincir değerlendirmesini bastırır ama imzalayan aramasını bastırmaz ve boş storeda CRL kontrolü hiç iptal çalıştırmadan her zinciri reddeder
Her tuzak kendinden emin bir yanlış hüküm verir: akış konumu güven başarısızlığı kılığına girer, no-verify bayrağı adının ima ettiğinden azını bastırır ve hiç çalışmamış iptal kontrolü, sorun bulan bir iptal kontrolü gibi görünür

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

Contents deliğinin iki yanındaki bayt aralıkları üzerinde VerifyDetached ile zaman damgası tokenları için VerifyAttached taşıyan IPdfCmsVerifier soyutlaması, SignatureStatus, TrustStatus ve RevocationStatus olmak üzere üç bağımsız hüküm ve CryptoAPI, SecTrust ya da ConfigureSslCmsVerifier üzerinden başlangıçta seçilen platform başına backendler gösteren PDFium VCL şeması
Interface iki doğrulama biçimiyle üç hüküm taşır; çünkü farklı sorulara cevap verirler ve ayrışabilirler; kurulan backend her hükmün yanına kaydedilir ki saklanan sonuçlar yeniden üretilebilsin

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