HotPDF, Delphi PDF componenti, artık imza wrappingi reddediyor: v2.759.0dan beri hem VerifyLoadedSignatureEx hem de toplu validator, iki /ByteRange segmenti arasındaki boşluğun sınırlayıcılar dahil tam olarak /Contents hex dizesi olmasını şart koşuyor ve v2.761.0, ikinci bir imzanın hâlihazırda imzalı bir PDFe temiz bir artımlı revizyon olarak eklenebilmesi için AddLoadedSignedSignatureFieldi getiriyor. İki değişiklik birbirine aittir, çünkü doğru bir ikinci imza, daha katı verifierın beklediği düzenin ta kendisidir
Problemi ortaya çıkaran durum sıradandır. Bir sözleşmeyi tedarikçi imzalar, sonra onaylayacak kişiye gider; ilk imzayı bozmadan karşı imza atması gerekir. İkinci revizyon birincinin ardına eklenir, kendi /ByteRangei büyümüş dosyanın tamamını kapsar ve her iki imza da doğrulanmalıdır. Bunu elle yapmak, artımlı bölümü kendiniz yazmak demekti ve tam olarak bunu yapan test fikstürü, eski verifierın memnunca kabul ettiği ders kitabı bir imza-wrapping yapısı çıktı. Doğrulama APIye daha önce bakmadıysanız HotPDF ile PDF dijital imzalarını doğrulama rehberi, bu yazının üzerine kurulu olduğu temelleri kapsar
ByteRange boşluğuna tam olarak ne girer?
Boşluk, tam /Contents değerini içermelidir, başka hiçbir şeyi değil: ISO 32000-1 §12.8.3.3, < ve > sınırlayıcılarıyla birlikte hex dizesinin iki bayt aralığı arasındaki alana tam oturduğunu söyler ve ISO 32000-2 §12.8.1 aynı kuralı ileri taşır. Tablo 252 ile PAdES belgeleri yalnızca özetin Contents değerini dışladığını söyler; bu, yalnızca hex rakamlarının dışlandığı olarak okunmaya müsaittir. Daha eski HotPDF sürümleri öyle okudu: PreparePDFForSigning ile streaming CMS hazırlığı köşeli parantezleri de hash ediyordu; kaynak yorumu parantezlerin kapsanması gerektiğinde ısrar ediyordu. Boşluğu imza değeriyle karşılaştıran validatorler o düzeni geçersiz bir bayt aralığı olarak işaretler; v2.759.0 bu yüzden iki sınırlayıcıyı da imzalı aralıkların dışına taşıdı. İmzalı herhangi bir dosyada hızlı bağımsız kontrol iki bayta bakmaktır: ByteRange[1] ofsetindeki bayt <, ByteRange[2] - 1 ofsetindeki bayt > olmalıdır
Boş olmayan boşluk kontrolü imza wrappingini neden göremez?
Boş olmayan bir boşluk kontrolü yalnızca özetten bir şeyler çıkartıldığını kanıtlar, ne çıkarıldığını değil; saldırı yüzeyinin tamamı budur. /Contents yer tutucusu binlerce sıfır rakamıyla rezerve edilir, gerçek bir CMS kapsayıcısı ise onu nadiren doldurur. Bir saldırgan o sıfır dolgusunun içinde hex dizesini bir >yle erken kapatabilir, rezerve alanın gerisine yeni objeler ya da sahte bir revizyon yazabilir ve bayt aralıklarına dokunmaz. CMS imzası yine doğrular, çünkü imzalı her bayt değişmemiştir, aralıklar hâlâ 0da başlayıp dosya boyutunda bitiyor ve eski HotPDF verifierı CoversWholeDocumentu True set edilmiş svValid bildiriyordu. Bir PDF okuyucusu ise o imzasız deliğe ne oturduysa onu ayrıştırır
HotPDF artık boşluğu bayt bayt doğrulanacak veri sayar. Verifier boşluğu okur, sınırlayıcıları soyar, yalnızca hex rakamlarıyla PDF boşluklarını (tab, line feed, form feed, carriage return, space) kabul eder, rakamları çözer ve sonucun imza sözlüğü /Contentsine tam olarak eşit olmasını şart koşar. Başka her şey sonucu svInvalidByteRangee düşürür. Kontrol hem tek imza yolunda hem de kendi kapsam mantığını koruyan ve aynı düzeltmeye ihtiyaç duyan ValidateLoadedSignatureBatchte çalışır. v2.759.0 öncesi HotPDFin ürettiği, boşluğu yalnızca rakamları taşıyan ve parantezleri aralıkların hemen içinde tutan dosyalar hâlâ doğrular; böylece arşiv belgeleri bir anda kırmızıya dönmez
var
Pdf: THotPDF;
Info: THPDFSignatureInfo;
Status: THPDFSignatureVerifyStatus;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('SignedTwice.pdf');
for I := 0 to Pdf.GetLoadedSignatureFieldCount - 1 do
begin
Status := Pdf.VerifyLoadedSignatureEx(I, Info);
case Status of
svValid:
if Info.CoversWholeDocument then
Writeln(Info.FieldName, ': valid, covers the whole file')
else
Writeln(Info.FieldName, ': valid, ',
Info.UnsignedTrailingBytes, ' bytes appended later');
svInvalidByteRange:
Writeln(Info.FieldName, ': ByteRange gap rejected (wrapping?)');
else
Writeln(Info.FieldName, ': failed, status ', Ord(Status));
end;
end;
finally
Pdf.Free;
end;
end;
Hâlihazırda imzalı bir PDFe ikinci imza nasıl eklenir?
İmzalı dosyayı BeginIncrementalUpdate ile açın, AddLoadedSignedSignatureFieldi çağırın, SaveIncrementalUpdate ile kaydedin, sonra hazırlanan dosyayı THotPDF.SignPDFWithPFX class fonksiyonuyla imzalayın. v2.761.0dan önce BeginIncrementalUpdateten sonra THPDFPage.AddSignedSignatureField çağırma reçetesi çalışamazdı, çünkü artımlı modda CurrentPage nildir ve hiçbir şey yüklenmiş bir belgedeki alana /V yer tutucusu bağlayamıyordu. Yeni metot widgetı yüklenmiş sayfada oluşturur ve yeni-belge yolunun kullandığı yer tutucu sözlüğünün aynısını /Vnin altına asar; böylece her iki imzalama yolu da tek serileştirmeyi paylaşır. İlk imzanın kendisi için Delphide PAdES dijital imzaları oluşturma yazısı PFX hattını adım adım anlatır
var
Pdf: THotPDF;
FieldIndex: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.BeginIncrementalUpdate('Signed.pdf');
// Sayfa 0, puantoda widget dikdörtgeni, CMS için 8192 bayt rezerve
FieldIndex := Pdf.AddLoadedSignedSignatureField(0, 320, 60, 520, 120,
'ApproverSignature', 8192);
if FieldIndex < 0 then
raise Exception.Create('Page index out of range');
Pdf.SaveIncrementalUpdate('Prepared.pdf');
finally
Pdf.Free;
end;
if not THotPDF.SignPDFWithPFX('Prepared.pdf', 'SignedTwice.pdf',
'approver.pfx', 'pfx-password') then
raise Exception.Create('Second signature failed');
end;
AddLoadedSignedSignatureField, kardeşlerinden bilinçli olarak daha sessizdir. Öteki AddLoaded* alan oluşturucuları AcroForm üzerine /NeedAppearances true set eder; bu, görüntüleyiciye alan appearancelarını yenilemesini söyler. İmzalı bir belgede o yenileme imzalı içeriği yeniden yazabilir; bu yüzden yeni metot bayrağı, kaynak hâlihazırda taşımadıysa yine kaldırır. /SigFlags özgün değerini OR 3 (SignaturesExist artı AppendOnly, ISO 32000-1 Tablo 219) olarak korur. Sayfada MarkDirty çağırmanıza da gerek yoktur: /Annotsa ve /Fieldse eklemek kirli bayrağı sahip dolaylı objeye taşır; açık bir sayfa işareti yalnızca değişmemiş bir sayfa sözlüğünü yeni revizyona çekerdi ve revizyon analizi bunu bir sayfa değişikliği olarak bildirirdi. Son olarak yer tutucu /ByteRangei /Contentsten önce yazar, çünkü patcher önce /ByteRange sentinelini bulur ve eşleşen hex dizesini ileriye doğru arar
Dış bir imzalayıcı ya da HSM CMSi ürettiğinde ne değişir?
İş akışında hiçbir şey değişmez ama ofsetler artık spesifikasyonun dediğini ifade eder. PreparePDFForSigning, boşluğu tam /Contents dizesi olan iki 0 tabanlı aralık döndürür ve ContentsHexStart, AnsiStringteki ilk hex rakamının 1 tabanlı indeksidir. Daha kısa bir CMS, kapanış >sinden önce, sonda 0 ile doldurulur. PreparePDFForSigning bulduğu ilk yamalanmamış sentinelı yamaladığı için revizyon başına tam olarak bir yer tutucu hazırlayın ve dosyada önceki imzalar varken arama temelli InsertSignatureHex yerine döndürülen ofsetlerle InsertSignatureHexAti tercih edin
var
Bytes, ToSign, CmsHex: AnsiString;
R1Start, R1Len, R2Start, R2Len, HexStart, HexLen: Integer;
begin
Bytes := LoadFileAsAnsiString('Prepared.pdf'); // sizin yardımcınız
if not THotPDF.PreparePDFForSigning(Bytes, R1Start, R1Len,
R2Start, R2Len, HexStart, HexLen) then
raise Exception.Create('No signature placeholder found');
// Boşluk bütün hex dizesidir: '<' aralık 1i bitirir, '>' aralık 2den önce gelir
Assert(Bytes[R1Start + R1Len + 1] = '<');
Assert(Bytes[R2Start] = '>');
ToSign := Copy(Bytes, R1Start + 1, R1Len) + Copy(Bytes, R2Start + 1, R2Len);
CmsHex := SignDetachedWithHsm(ToSign); // sizin CMS imzalayıcınız, hex DER
if not THotPDF.InsertSignatureHexAt(Bytes, HexStart, HexLen, CmsHex) then
raise Exception.Create('CMS does not fit the reserved space');
SaveAnsiStringToFile(Bytes, 'SignedTwice.pdf'); // sizin yardımcınız
end;
Yeni kontrollerin sınırları nerede?
Boşluk kontrolü belirli bir deliği kapatır ve fazla satılmamalıdır. svValid hâlâ bayt bütünlüğü artı gömülü sertifikayla eşleşen bir anahtar demektir; o sertifikaya duyulan güven ayrı bir karardır. Boşluk, yalnızca verifierın kaynak baytları olduğunda doğrulanır; VerifyLoadedSignatureEx onları yüklenmiş dosyadan okur, TStream overloadları sizden alır. Karşı imzalı dosyadaki ilk imza için CoversWholeDocument doğru biçimde Falsetur ve eklenen revizyonun yalnızca imza mı eklediği yoksa sayfaları mı değiştirdiği, HotPDFte DocMDP, FieldMDP ve revizyon analizinin sorusudur. Ayrıca eklenen PDF MAC kontrolü ofsetleri < ile > konumlarıyla karşılaştırır, yani hem eski hem yeni düzeni kabul eder; v2.759.0 öncesi ofsetleri sabit kodlayan kendi aracınız, taze imzalı bir dosyayla karşılaştığında ilk başarısız olan o olur
Delphi ya da C++Builder uygulamanız PDF imzalıyor, karşı imzalıyor ya da denetliyorsa en güvenli yol, aynı düzeni tek bir kütüphaneye üretip doğrulatmaktır. HotPDF, yerel Delphi PDF componenti, daha katı boşluk doğrulamasını, artımlı ikinci imzaları ve yukarıda gösterilen dış-imzalayıcı kancalarını tek componentte taşır