Teknik Makale

PDF imza wrappingi: ByteRange boşlukları ve ikinci imzalar

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

HotPDFte doğru doldurulmuş bir PDF imza ByteRange anatomisi: ilk aralık dosyayı sıfırıncı bayttan kapsar, boşluk küçüktür-büyüktür sınırlayıcıları dahil tam /Contents hex dizesini tutar, ikinci aralık traileri sona dek kapsar ve ByteRange[1] ile ByteRange[2] - 1deki iki tek baytlık kontrol düzeni her imzalı dosyada doğrular
v2.759.0dan beri sınırlayıcılar imzalı aralıkların dışında durur; özet yalnızca rakamları kapsar ve boşluk bayt bayt doğrulanabilir

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

İmza wrappingi Delphide gevşek kontrol edilmiş bir PDF ByteRangeni nasıl sömürür: saldırgan hex dizesini binlerce rezerve sıfır rakamının içinde erken kapatır, kapsanan hiçbir bayta dokunmadan imzasız boşluğa sahte bir revizyon yazar; eski HotPDF kontrolü v2.759.0 boşluğu bayt bayt doğrulamaya başlayana dek CoversWholeDocument true ile svValid bildiriyordu
Boş olmayan bir boşluk yalnızca özetten bir şeyler çıkartıldığını kanıtlar, neyi değil — dolgulu delik saldırı yüzeyinin tamamıdır
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

Hâlihazırda imzalı bir PDFe Delphide karşı imza atma HotPDF iş akışı: BeginIncrementalUpdate dosyayı açar, AddLoadedSignedSignatureField widgetı oluşturup /Contents yer tutucusunu rezerve eder, SaveIncrementalUpdate ikinci revizyonu ekler ve SignPDFWithPFX doldurur; yeni ByteRange büyümüş dosyanın tamamını kapsarken ilk imza UnsignedTrailingBytes ile geçerli kalır
Revizyon başına tek yer tutucu, her iki imzalama yolunda da aynı serileştirmeyle hazırlanıp yamalanır — daha katı verifierın beklediği temiz düzen

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