Uyumluluk doğrulamasını dijital imzalamaya zincirleyen bir çalışma tezgâhı, dört adımı bu sırayla koordine etmeli ve baştan sona tek bir bayt kümesine bağlı tutmalıdır: bir PDF/A veya PDF/UA ön kontrolü çalıştırır, bulguların gerektirdiği düzeltmeleri uygular ve düzeltilmiş bir revizyonu kaydeder, tam olarak o revizyonu imzalar, ardından imzalanan dosyayı geri okuyup imzanın onu gerçekten kapsadığını doğrular. Bu sıra kozmetik değildir: geri okumayı atlarsanız kendi yazma yolunuza güvenmiş olursunuz; ön kontrolü yanlış revizyona karşı çalıştırırsanız uyumluluk raporunuz hiç göndermediğiniz bir dosyayı tarif eder
Ev yapımı boru hatlarının çoğunun yanlış yaptığı kısım, doğrulama ile imzalama arasındaki dikiş yeridir. Aralarında bir düzeltme geçişi olan iki ayrı araç olarak çalıştırırsanız, dosyanın her biri kendi baytlarına sahip en az üç farklı revizyonu ortaya çıkar. Bir denetçiye verdiğiniz ön kontrol raporu bunlardan birini tarif eder. İmza bir başkasını dondurur. Dosyada bunların aynı revizyon olduğunu belirten hiçbir şey yoktur ve çoğu zaman aynı da değildirler. Delphi ve C++Builder için losLab PDF Geliştirici Kütüphanesi olan PDF Library for Delphi, ön kontrolü ve PAdES imzalamayı tek bir cephe sınıfının arkasına yerleştirir; böylece tüm dizi, hangi baytlardan bahsettiğini asla kaybetmeyen tek bir süreçte yaşayabilir. Aşağıdaki her çağrı bugün kütüphanede mevcuttur ve yanında belirtilen her tuzak da öyle
Tek bir belgenin üç revizyonu ve boşluk nasıl açılır
Kayıtları sayın. Orijinal, yukarı akıştan gelir. Düzeltme geçişi onu yükler, bir uyumluluk modunu açar ve düzeltilmiş bir revizyon yazar. İmzalama geçişi, üçüncü bir yazma olan artımlı bir güncelleme olarak imza ekler. Üç kayıt, üç bayt düzeni ve bir ön kontrol raporu, üçünden hangisini kapsadığını belirtmedikçe hiçbir anlam taşımaz. Her ön kontrol çalıştırmasının ve her imzanın yanına kaydedilen dosyanın SHA-256'sı, doğruladığınız revizyonun imzaladığınız revizyon olduğunu kanıtlamanızı sağlayan ucuz bir çapadır
Kütüphanenin bir davranışı bu disiplini daha da sıkılaştırır. SetPDFAMode veya SetPDFUAMode üzerinden istenen uyumluluk düzeltmeleri, onları çağırdığınız anda etkili olmaz; kayıt sırasında uygulanır. Ek açıklama yazdırma bayraklarını zorlamak veya bir PDF/UA sekme sırası atamak gibi otomatik onarımlar yalnızca çıktı dosyasına iner, başka hiçbir yere inmez; bu yüzden belleğinizde az önce "düzelttiğiniz" belgeye karşı çalıştırılan bir kontrol, imzalayana gidecek baytlar hakkında hiçbir şey söylemez. Önce kaydedin, sonra kaydedilen dosyayı ön kontrolden geçirin. Bellekteki durum bir taslaktır; yalnızca diskteki dosya gerçektir
Diskten ön kontrol ve iki şey anlamına gelen sıfır
Düz ön kontrol giriş noktası CheckFileCompliance(FileName, Password, ComplianceTest, Options) çağrısıdır. Test 1, PDF/A'yı (ISO 19005) seçer, test 2 PDF/UA'yı (ISO 14289) seçer. Dosyayı kütüphanenin akış okuyucusu üzerinden açar, bu yüzden önce LoadFromFile çağırmaya gerek yoktur ve her girişte bir bulgu taşıyan bir dize listesi tanıtıcısı döndürür:
var
PDF: TPDFlib;
ListID, I: Integer;
begin
PDF := TPDFlib.Create;
try
ListID := PDF.CheckFileCompliance('invoice-fixed.pdf', '', 1, 0); // 1 = PDF/A
if ListID = 0 then
begin
if PDF.LastErrorCode <> 0 then
raise Exception.Create('Preflight could not read the file')
else
Writeln('No PDF/A findings');
end
else
begin
for I := 0 to PDF.GetStringListCount(ListID) - 1 do
Writeln(PDF.GetStringListItem(ListID, I));
PDF.ReleaseStringList(ListID);
end;
finally
PDF.Free;
end;
end;
Tuzak, dönüş değerinde yatar ve bu, her mutlu yol testini geçen türden bir tuzaktır. Sıfır "bulgu yok" anlamına gelir. Sıfır ayrıca "dosya açılamadı" anlamına da gelir, çünkü uygulama, bir okuma hatası da dahil olmak üzere sonuç listesi boş döndüğünde 0 döndürür. Sıfırı yeşil ışık olarak okuyan bir çalışma tezgâhı, başka bir sürecin kilitlediği bir dosyayı seve seve onaylar. Yukarıdaki gibi çağrıyı LastErrorCode ile eşleştirmek, iki durumu birbirinden ayıran şeydir. Denetleyici ayrıca dosyayı yazmaya kapalı bir paylaşım moduyla açar; bu yüzden düzeltme adımınız hâlâ bir yazma tanıtıcısını tutuyorsa, ön kontrol uyumlulukla hiçbir ilgisi olmayan, serbest bırakmayı unuttuğunuz bir akışla tamamen ilgili bir nedenle başarısız olur
Bulguları bir boru hattı değil de bir kişi okuması gerektiğinde, CreatePreflightReport onları okunabilir bir rapor olarak oluşturur. ComparePreflightReports ise iki çalıştırmayı karşılaştırır; bu, düzeltmenin orijinal bulguları, sessizce yenilerini eklemeden temizlediğini göstermenin düzenli bir yoludur
Kontrol edilen revizyonu bir SignProcess ile imzalamak
Kaydedilen revizyon ön kontrolü geçip özeti kayda geçtiğinde, tam olarak o dosyayı imzalayın, başka hiçbirini değil. SignProcess API'si bir builder gibi okunur: bir süreç tanıtıcısı açın, onu satır satır yapılandırın, işleyin, ardından sonuç kodunu geri okuyun
ProcessID := PDF.NewSignProcessFromFile('invoice-fixed.pdf', '');
if ProcessID = 0 then
raise Exception.Create('Cannot open source for signing');
PDF.SetSignProcessField(ProcessID, 'ApprovalSig');
PDF.SetSignProcessPFXFromFile(ProcessID, 'company.pfx', PfxPassword);
PDF.SetSignProcessInfo(ProcessID, 'Invoice approval', 'Berlin', 'billing@example.com');
PDF.SetSignProcessCustomSubFilter(ProcessID, 'ETSI.CAdES.detached'); // PAdES temel düzey
PDF.SetSignProcessDigestAlgorithm(ProcessID, 2); // SHA-256
PDF.SetSignProcessReserveContentsBytes(ProcessID, 8192); // sonraki bir zaman damgası için yer
PDF.EndSignProcessToFile(ProcessID, 'invoice-signed.pdf');
if PDF.GetSignProcessResult(ProcessID) <> 1 then
Writeln('Sign failed, code ', PDF.GetSignProcessResult(ProcessID));
PDF.ReleaseSignProcess(ProcessID);
Bu dizideki iki satır göründüğünden daha fazla ağırlık taşır. ETSI.CAdES.detached ile SetSignProcessCustomSubFilter, eski adbe.pkcs7.detached ailesi yerine ETSI EN 319 142-1'de profillendiği gibi bir PAdES imzası seçer; bu, bir Avrupa doğrulayıcısının kabul ettiği imza ile işaretlediği imza arasındaki farktır. SetSignProcessReserveContentsBytes, /Contents yer tutucusunu doldurur ve burada seçtiğiniz boyut geleceğe dair bir karardır: bir imza zaman damgası bir gün eklenecekse, büyütülmüş CMS şimdi ayırdığınız alana sığmak zorundadır, çünkü yer tutucu daha sonra her şeyi yeniden imzalamadan büyüyemez. Cömertçe ayırırsanız birkaç kilobayt harcarsınız. Çok dar ayırırsanız zaman damgası adımı aylar sonra, bu tek satıra bağlamakta zorlanacağınız bir taşmayla başarısız olur
GetSignProcessResult, bir boole değil bir kodla yanıt verir ve bu kodları saklamaya değer. 1 başarıyı gösterir. 4 yanlış bir PDF parolasını, 7 yanlış bir sertifika parolasını, 9 özel anahtar taşımayan bir PFX'i, 11 imza uygulanırken oluşan bir hatayı gösterir. Bunları doğru/yanlış ikilisine indirgerseniz, yanlış parola destek vakasını özel anahtarsız bir anahtar vakasından ayıran o tek bilgi parçasını atmış olursunuz. Tamsayıyı günlükleyin
Geri okuma: az önce ürettiğiniz dosyayı denetlemek
Hiçbir çalışma tezgâhı, onaylamak üzere olduğu dosyayı yazan yola güvenmemelidir. Denetim sınıfı TPDFlibSignDoc, imzalanan çıktıyı yeniden açar ve imza sözlüğü girişlerini doğrudan diskten okur:
var
Doc: TPDFlibSignDoc;
Names: TStringList;
FS: TFileStream;
I: Integer;
SourceSize, RangeStart, GapStart, TailStart, TailLen: Int64;
begin
// Boyutu Open'dan önce yakalayın: denetim nesnesi dosya üzerinde bir paylaşım kilidi tutar
FS := TFileStream.Create('invoice-signed.pdf', fmOpenRead or fmShareDenyNone);
SourceSize := FS.Size;
FS.Free;
Doc := TPDFlibSignDoc.Create;
Names := TStringList.Create;
try
if not Doc.Open('invoice-signed.pdf', '', False) then Exit;
Doc.GetSignatureFieldNames(Names);
for I := 0 to Names.Count - 1 do
if Doc.GetSignatureValueObjNum(Names[I]) > 0 then // > 0 alanın imzalandığı anlamına gelir
begin
RangeStart := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 11)));
GapStart := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 12)));
TailStart := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 13)));
TailLen := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 14)));
if (RangeStart = 0) and (TailStart + TailLen = SourceSize) then
Writeln(Names[I], ': signature covers the file to EOF')
else
Writeln(Names[I], ': earlier revision, or unusual ByteRange layout');
end;
Doc.Close;
finally
Names.Free;
Doc.Free;
end;
end;
ValueKey bağımsız değişkenleri sözlük girişleriyle eşleşir. Anahtar 0, /Contents'ten ham CMS'yi döndürür; 2 ve 3 anahtarları /Filter ve /SubFilter adlarını, 11 ile 14 arasındakiler ise dört ByteRange sayısını döndürür. Metin değerleri bunun yerine GetSignatureTextValueByName üzerinden geri gelir: anahtar 0 iddia edilen imzalama zamanıdır ve anahtar 5, bir belge her ikisini de taşıdığında önem kazanan sıradan bir Sig'i bir DocTimeStamp'tan ayırt eder
Bu örneğin başındaki dosya boyutu yakalama işlemi, ev idaresi değil, taşıyıcı bir unsurdur. TPDFlibSignDoc.Open, dosyayı tüm yaşam döngüsü boyunca kısıtlayıcı bir paylaşım kilidi altında tutar; bu yüzden ham baytlara ihtiyaç duyan her şey (imzalanan aralığı özetlemek, CMS özetini yeniden hesaplamak) dosyayı Open çağrılmadan önce okumak zorundadır. Kütüphanenin kendi SigningWorkbench demosu tam da bu nedenle önce dosyanın tamamını belleğe okur ve sıralamayı görmezden gelen bir çalışma tezgâhı, yarışı hangi makine kaybederse orada aralıklı olarak başarısız olur
Kapsamayı kanıtlayan ByteRange aritmetiği
Sağlıklı, tek imzalı bir dosya, [0 a b c] biçiminde bir ByteRange'e sahiptir: kapsama 0 ofsetinde başlar, a ile b arasındaki onaltılık /Contents yer tutucusunu atlar, ardından b+c baytına kadar devam eder. b+c dosya boyutuna eşit olduğunda, imza dosya sonuna kadar her şeyi kapsar; istediğiniz sonuç budur. Kısa kaldığında ise imza yazıldıktan sonra biri artımlı bir güncelleme eklemiştir. Bu, ISO 32000-1§12.8 kapsamında tamamen meşrudur, çünkü sonraki form doldurmaları, ikinci bir imza ve bir DSS sözlüğü tam olarak bu şekilde gelir. Bu aynı zamanda bir denetim izinin, bir anlaşmazlık sırasında baskı altında yeniden kurgulanmak yerine imzalama anında kaydetmesi gereken bir gerçektir
Bu aritmetiği yaparken tamsayı genişliğine dikkat edin. Düz API'nin GetSignProcessByteRange'i 32 bit'lik bir Integer döndürür, ama alttaki değerler Int64'tür; bu yüzden 2 GB'ı aşan bir dosyada düz erişimci sessizce kırpar. Bunun yerine Int64 döndüren sınıf katmanındaki TPDFlibSigner.GetByteRange'e başvurun veya yukarıdaki denetim kodunun yaptığı gibi değerleri GetSignatureValueByName'den ayrıştırın
Kütüphanenin size bıraktıkları
İki sınırın, son sprintte değil tasarım aşamasında öğrenilmesi daha iyidir. Düz TPDFlib API'si hiçbir imza doğrulama sarmalayıcısı taşımaz. Kriptografik doğrulama bir katman aşağıda, VerifySignature'ı geçerli, geçersiz veya bilinmiyor yanıtını veren TPDFlibSignatureVerifier içinde yaşar. RFC 3161 zaman damgası otoriteleri için yerleşik bir HTTP istemcisi de yoktur. Kütüphane, gönderilecek özeti hesaplar ve bir jeton geri geldiğinde genişletilmiş CMS'yi yeniden gömer, ama TSA'ya giden ağ gidiş dönüşünü yazmak size kalır. İkisini de sarmalamak basittir ve bir sürümden bir hafta önce eksik olduklarını fark etmek gerçekten tatsızdır; bu yüzden onları ilk taslaktan itibaren tasarıma dahil edin
Uyumlulukla ilgili bir soru açıkça çözülmeye değer, çünkü son kapının nereye konacağına o karar verir: bir imza eklemek PDF/A'yı bozar mı? Tek başına hayır. İmza artımlı bir güncelleme olarak gelir ve ISO 19005-2'den itibaren imzalı belgelere açıkça izin verilir. Buradaki incelik, imza görünümünün, gömülü yazı tipleri ve cihaza bağımlı olmayan renk dahil olmak üzere diğer herhangi bir sayfa içeriğiyle aynı kurallara tabi olmasıdır. Yani çalışma tezgâhındaki son kapı, bu kez imzalı çıktıya karşı çalıştırılan bir ön kontrol daha yapmaktır. CheckFileCompliance'ı hızlı, boru hattı içi kontrol olarak ele alın ve yine de sürüm adaylarını veraPDF gibi bağımsız bir araçla doğrulayın, çünkü doğrulayıcılar örtüşen ama aynı olmayan kural kümeleri uygular; ikisi anlaşmazlığa düştüğünde, bulgu metni genellikle okunması gereken maddeyi belirtir
Tüm bunlardan bir sıralama noktası ortaya çıkar. İmzalama ve zaman damgalama tek bir geçiş değildir: önce temel imza yazılır, ardından ayrı bir zaman damgası süreci, ayrılmış /Contents alanının içindeki CMS'yi genişletir; bu da az önceki reserve-bytes satırının neden bu kadar ağırlık taşıdığını tam olarak açıklar. Bu çalışma tezgâhı üzerine kurulan zaman damgası ve uzun vadeli doğrulama katmanları için PAdES imzalama ve doğrulama kılavuzu imzayı temel düzeyden B-LT'ye taşır ve ön kontrol yarısı PDF/A ve PDF/UA ön kontrol kılavuzunda daha derine iner. Tam API belgeleri ve deneme sürümü indirmeleri PDF Library for Delphi ürün sayfasında bulunur