PDFium VCL, PAdES belgelerini macOS Keychain'de tutulan bir özel anahtarla, her Security ve CoreFoundation sembolünü çalışma zamanında dlopen ile dlsym aracılığıyla çözümleyen bir backend üzerinden imzalar. Hiçbir şey link zamanında bağlanmaz; bu, yanlış yazılmış bir sembol adının linker hatası ya da çökme olarak değil, KeychainAvailable'ın False döndürmesi ve KeychainMissingSymbols'ın suçluyu adıyla bildirmesi olarak ortaya çıkarır
O seçim, rahatsız edici bir kısıt tarafından dayatıldı ve idare ediş biçimi geneller. Unit, macOS SDK'sı olmayan bir makinede yazıldı; her framework sembol adı ve her sabit belgelerden geldi ve hiçbiri bir header'a karşı denetlenemedi. O durumda yanlış cevap, kodu dikkatlice yazıp ummaktır. Doğru cevap, kaçınılmaz hataların kendilerini mümkün olan en kolay bulunur biçimde ilan etmelerini ayarlamaktır
Dinamik bağlamanın hedef platformda bile doğru karar olması
Çünkü programı durduran bir hata sınıfını, kendini bildiren bir hata sınıfına çevirir. Yanlış bir statik bağlanmış framework referansı, hedefte link zamanında kalır ve başka hiçbir yerde bağlanmaz. Yanlış bir dinamik bağlanmış olan ise kullanılamayan bir backend ile çözümlenmemiş adlar listesi üretir ve Mac'teki ilk çalıştırma, soruyu bu neden kullanılamıyor durumundan bir yazım hatasını adlandıran tek satıra indirger
Bir kez değil her gün karşılığını ödeyen ikinci bir kazanç daha var. Unit hiçbir framework bağlamadığı için her platformda derlenir; sıradan Windows derlemesi böylece sözdizimini, türlerini ve uses cümlesini denetlemeye devam eder. Yalnızca ekipten kimsenin sahip olmadığı bir platformda derlenen unit, ona bakan hiçbir derleyicisi olmayan unit'tir ve paylaşılan her tür refactor'unda sessizce çürür
uses
FPdfCrypto, FPdfCryptoMac;
var
Options: TPadesSignerOptions;
begin
if not KeychainAvailable then
raise Exception.Create('Keychain backend unavailable, unresolved: ' +
KeychainMissingSymbols);
ConfigureKeychainSignerProvider; // PAdES signer backend olarak kurulur
ConfigureKeychainCmsVerifier; // ve doğrulama backendi olarak
Writeln('signer backend : ', PadesCryptoBackendName);
Writeln('verify backend : ', PadesCmsVerificationBackendName);
Options := TPadesSignerOptions.Default;
Options.CertificateThumbprint := 'B1 3F 9C ...'; // SHA-1, harf durumu fark etmez
Options.PaddingScheme := psRsaPss;
end;
İki tür export sembolü, onları okumanın iki yolu
Bu, bütün bağlamadaki tek en kafa karıştırıcı ayrıntıdır ve tersini kurmak temiz derlenir, çalışma zamanında kalır. CoreFoundation ile Security, aynı dlsym çağrısı üzerinden kategorik olarak farklı iki şey export eder ve kodun hangisinin ne olduğunu bilmesi gerekir
Keychain öğe sınıfı anahtarları ile CoreFoundation boolean singletonları gibi adlandırılmış sabitler, içerikleri istediğiniz CFStringRef ya da CFBooleanRef olan export edilmiş değişkenlerdir. dlsym o değişkenin adresini döndürür; değeri elde etmek için bir kez dereference etmelisiniz. Dictionary key ile value callback'leri gibi callback tablosu yapıları ise export edilmiş yapılardır ve dlsym yapının adresini döndürür; dictionary oluşturma işlevinin beklediği pointer da tam olarak budur. Onu dereference ederseniz yapının ilk makine kelimesini bir pointer varmış gibi geçirirsiniz
Hataların hiçbiri derleme hatası üretmez, hiçbiri net bir çalışma zamanı hatası da üretmez. Elinize, aşağı akışta bir yerde kalacak çöp bir pointer geçer. Ayrımı yanlış yapmayı imkansız kılmanın yolu, hatırlamaya güvenmeyi bırakmaktır: biri bağlayıp dereference eden, öteki bağlayıp etmeyen iki yardımcı işlev; çağrı noktası hangi tür sembolü istediğini bildirir, yardımcı gerisini dayatır
// Export değişkeni: dlsym, CFTypeRef'i tutan değişkenin adresini verir;
// bir kez dereference edin
FSecClassKey := BindConstant(SecurityLib, 'kSecClass');
// Export yapı: dlsym, yapının KENDİSİNİN adresini verir; API'nin
// istediği de budur. Dereference etmeyin
FKeyCallbacks := BindStruct(CoreFoundationLib,
'kCFTypeDictionaryKeyCallBacks');
Bir RSA-PSS imzası neden iki ayrı yedeğe ihtiyaç duyar?
Çünkü algoritma iki bağımsız şekilde yok olabilir ve bunlardan yalnızca biri sürüm sorusudur. PSS digest-signing algoritma sabiti macOS 10.13'te belirdi; daha eski bir sistemde sembol sadece orada değildir ve bağlama nil alır. Bu sürüm kontrolüdür. Ayrı olarak, sabitin var olduğu bir sistemde belirli bir anahtar yine de onu reddedebilir ve framework o soruyu o anahtar için SecKeyIsAlgorithmSupported üzerinden cevaplar. Donanımla desteklenen bir anahtar ya da kısıtlayıcı nitelikli bir anahtar, aynı makinedeki bir yazılım anahtarı PSS'i kabul ederken onu reddedebilir
Her iki yol da aynı yedeğe varmak zorundadır: PKCS#1 v1.5'e geç. Ve kritik kısım şudur: yedek, yalnızca imzalama çağrısını değil CMS yapısına yazılan algoritma tanımlayıcısını da değiştirmek zorundadır. Gerçekte v1.5 imza üretirken PSS algoritma tanımlayıcısı yazmak, her doğrulayıcının haklı olarak reddettiği bir belge verir; bu, PSS desteklenmiyor bildirmekten kat kat kötüdür. Bir düşüş kabul edilebilir; bildirdiğiniz ile yaptığınız arasındaki uyuşmazlık değildir; bu macOS cilvesi değil, imza kodu için genel bir kuraldır. İmza düzeyindeki sonuçlar PAdES B-B ile PDF imzalama makalesinde serimlenir
ECDSA imza kodlaması ve kayda değer bir terslik
Eliptik eğri yolu macOS'ta hiçbir dönüşüm gerektirmez ve bu, bir PKCS#11 bağlamasının gerektirdiğinin tersidir. Security framework'ünün ECDSA için digest-signing algoritması imzayı zaten X9.62 DER biçiminde döndürür; CMS'in istediği de tam olarak budur. Bir PKCS#11 token ise ham sabit genişlikte P1363 çiftini döndürür; bir imza yapısına girmeden önce yeniden kodlanması gerekir
Aynı interface'i uygulayan iki backend, aynı algoritma için zıt muamele ister ve hiçbiri yanlış değildir. Bir soyutlamanın sergilemek yerine yutmak zorunda olduğu fark tam olarak budur: PAdES katmanı bir provider'dan imzalamasını ister ve kodlama konvansiyonları provider'ın içinde kalır. Yukarı sızarlarsa her çağıran, backend başına koşullu taşımakla biter. Aynı şekil, HSM'ye karşı uzak PAdES imzalama oturumları makalesinde anlatılan uzak imzalama hikayesinde de görünür
// Provider interface'i her platformda aynıdır; seçim çağrı başına değil
// başlangıçta verilen bir karardır
{$IFDEF DARWIN}
if KeychainAvailable then
ConfigureKeychainSignerProvider;
{$ENDIF}
{$IFDEF MSWINDOWS}
// Windows CNG provider'ı platform unit'i tarafından kurulur
{$ENDIF}
if not PadesCryptoAvailable then
raise Exception.Create('no signing backend on this platform');
// Buradan itibaren imzalama kodu platform-nötrdür
Signer := ResolvePadesSigner(Options);
Üç satır arayla duran referans sayımı kuralları
Core Foundation bellek yönetimi adlandırma konvansiyonlarını izler ve buradaki tuzak, farklı konvansiyonlu işlevlerin aynı kısa blokta yan yana görünmesidir. Bir trust nesnesinden sertifika alan (get) işlev, serbest bırakılmaması gereken ödünç bir referans döndürür. İmzalayan sertifikasını kopyalayan (copy) ya da verisini kopyalayan işlevler ise serbest bırakılması gereken sahipli referanslar döndürür. Art arda üç çağrı, iki sahiplik kuralı ve ödünç olanı serbest bırakmak o satırda başarısız olmaz; bir retain sayısını bozar ve alakasız bir şeyi daha sonra yok eder
Panzehir, temizlik kodunu yazmadan önce her seferinde, istisnasız her framework işlev adındaki fiili okumaktır. Bu, bir API'nin kopya mı görünüm mü döndürdüğünü kontrol etmenin CoreFoundation karşılığıdır ve yanılmak hataya değil aralıklı bir çökmeye mal olur
Bu backend'in iddia etmedikleri
Yazıldığı anda macOS'ta hiç çalışmadı ve bunu açıkça söylemek, ima yollu bir güvenceden daha faydalıdır. Kanıtlanabilir olan daha dardır ve hâlâ değerlidir: unit, günlük derlemenin parçası olarak Windows'ta derlenir; her framework sembolü, başarısızlıklar sayılarak çalışma zamanında ada göre bağlanır; iki PSS yedeği dahil algoritma seçim mantığı, incelenebilir ve üzerinde akıl yürütülebilir sıradan Pascal'dır. Mac'teki ilk çalıştırma ya işler ya da düzeltilecek adlar listesi üretir
CMS yapısını elle kurmak yerine daha yüksek düzeyli CMS çözücüsünü kullanan doğrulama tarafı, macOS'ta SecTrust ile PDF imzalarını doğrulama makalesinde ele alınır ve aynı bağlama altyapısını, aynı teşhis yaklaşımını paylaşır
Buradan taşınabilir fikir, macOS'tan çok risk yerleşimiyle ilgilidir. Doğrulayamadığınız bir interface'e karşı kod yazmak zorunda kaldığınızda, hataların en ucuz bulunabildiği kurguyu seçin. Çözümlenmemiş adların açık bir listesiyle dinamik bağlama, doğrulanamayan yirmi varsayımı tek bir teşhis satırına çevirir. İki backend de PDFium Delphi bileşeni ile kaynak olarak gelir; bir sembol adının düzeltilmesi gerekirse bu, destek talebi değil kendi ağacınızda tek satırlık bir değişikliktir