Teknik Makale

Delphi'de PDF MAC Revizyon Zinciri Doğrulaması (ISO 32004)

HotPDF, ISO/TS 32004 PDF MAC doğrulamasını dosya başına değil revizyon başına yapar. THotPDF.ValidatePDFMACChain, zincir çapasından ileriye doğru her artımlı güncellemeyi gezer ve her MAC öğesini, o revizyonun kendi startxref ve %%EOF işaretinde biten salt okunur bir önek akışına karşı doğrular. En yeni revizyondaki tek bir geçerli MAC, altındaki revizyonlar hakkında hiçbir şey kanıtlamaz

Bütün bunları motive eden senaryo şu. Üzerinde PDF MAC bulunan AES-256 şifreli bir PDF dağıtıyorsunuz. Birisi dosyayı bir hex düzenleyicide açıyor, ilk MAC korumalı revizyonun içinde bir baytı çeviriyor, ardından kendi mükemmel geçerli MAC değerini taşıyan yepyeni bir revizyon ekliyor. Her görüntüleyici dosyayı şikayetsiz açar ve geçerli bayt aralığını etkin kuyruktaki MAC ile karşılaştıran saf bir denetçi başarı bildirir; çünkü o MAC, kapsadığı baytlar için gerçekten doğrudur. Hasar iki revizyon aşağıda, kimsenin yeniden denetlemediği bir bölgede durur

Geçerli bir üst düzey MAC dosyanın sağlam olduğunu neden kanıtlamaz?

Çünkü PDF MAC bir belgeyi değil bir öneki kapsar. Artımlı güncelleme biçimin birinci sınıf bir parçasıdır: her kaydetme yeni bir gövde, yeni bir çapraz başvuru bölümü ve yeni bir kuyruk eklerken eski baytlar tam olarak oldukları yerde kalır. ISO/TS 32004 bu modelin üzerine biner; bu yüzden her revizyon, dosyayı o anki haliyle doğrulayan kendi /AuthCode sözlüğünü taşır ve yalnızca en yeniyi doğrulamak her önceki revizyonu incelenmemiş bırakır. HotPDF bu yüzden iki soruyu iki çağrı olarak sunar ve aralarındaki fark bu makalenin tamamının özüdür. ValidatePDFMAC «geçerli revizyon özgün mü» sorusunu yanıtlar ve bir THPDFPDFMACValidationInfo kaydı doldurur; ValidatePDFMACChain «bu dosyadaki MAC korumalı her revizyon özgün mü» sorusunu yanıtlar ve THPDFPDFMACChainValidationInfo kaydını revizyon başına bir dizi artı makine tarafından okunabilir bir hata nedeniyle doldurur. Yukarıdaki değiştirilip yeniden MAC eklenmiş dosyada ilk çağrı True döndürür, ikinci çağrı ise revizyon dizini 1 için False döndürür

var
  Pdf: THotPDF;
  Chain: THPDFPDFMACChainValidationInfo;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if not Pdf.ValidatePDFMACChain('incoming.pdf', 'user', Chain) then
    begin
      // Hata şunlardan biridir: pmcfRevisionBoundary, pmcfNoPDFMAC,
      // pmcfRequiredRevisionMissing, pmcfRevisionInvalid,
      // pmcfKDFSaltChanged, pmcfDigestDowngrade, pmcfPermissionDowngrade
      Writeln('chain rejected: ', Chain.Message);
      Writeln('revision ', Chain.FailureRevisionIndex,
              ' at xref offset ', Chain.FailureXRefOffset);
      Exit;
    end;
    for I := 0 to High(Chain.Revisions) do
      Writeln(I, ' len=', Chain.Revisions[I].RevisionLength,
              ' mac=', Chain.Revisions[I].HasPDFMAC,
              ' perms=', Chain.Revisions[I].PermissionsAuthenticated);
  finally
    Pdf.Free;
  end;
end;

Her MAC kendi önek akışında doğrulanır, asla son dosya uzunluğunda değil

Bu alandaki en pahalı hata, eski bir revizyonu yeniden karma yaparken üst sınır olarak son dosya boyutunu kullanmaktır; bu, sondaki baytları en yeni hariç her özetin içine katar ve sağlam bir dosyada kurcalama bildirir. HotPDF bunun yerine her revizyon için o revizyonun kendi startxref değerinde ve ardından %%EOF işaretinde biten sınırlı salt okunur bir akışı yeniden kurar ve yalnızca onu karma yapar. Sınırı bulmak göründüğünden daha zahmetlidir: %%EOF değişmezi bir içerik akışının ya da dizginin içinde görünebilir; bu yüzden bir aday ancak hemen önceki startxref değeri, doğrulanan bölümün çapraz başvuru ofsetine eşit bir sayıya ayrıştığında ve aralarında boşluktan başka bir şey olmadığında kabul edilir. Revizyon sonra işaretin ardından tam olarak bir satır sonu dizisini — tek CR, tek LF ya da bir CRLF çifti — içerir ve fazlasını içermez. Bu son kural pratikte ısırır; çünkü iki revizyon arasına fazladan boş satır yayan bir yazıcı, sonraki revizyona ait baytlar üretmiştir ve tüm sondaki boşluğu öncekine yutmak her iki özeti de sessizce değiştirir. Bölüm numaralandırması aynı disiplini izler: HotPDF çapraz başvuru bölümlerini eskiden yeniye tam olarak bir kez gezer; free, direct ve object-stream girdilerini yeniden oynatır, böylece sonraki bölümler önceki durumun üzerine yazar ki bu, aktif xref ayrıştırıcısının uyguladığı ilk görülen kazanır anlambiliminin tersidir

HotPDF her ISO 32004 PDF MAC öğesini o revizyonun kendi startxref ve dosya sonu işaretinde biten bir önek akışına karşı doğrular; böylece revizyon 1 içinde çevrilen bir bayt, en yeni MAC hâlâ temiz doğrulansa bile zinciri bozar
Her revizyonun MAC öğesi kendi sınırlı öneki üzerinde yeniden karma yapılır; bu yüzden revizyon 1 öğesini düzenleyip yeni MAC ekli bir revizyon eklemek ValidatePDFMAC işlevini yine memnun ederken ValidatePDFMACChain revizyon 1 üzerine iner

Zincir nereye çapalanır ve ne onu kırar?

Geçerli bir /AuthCode taşıyan ilk revizyon çapadır ve FirstMACRevisionIndex korumanın nerede başladığını bildirir; öncesindeki her şey yapı gereği korumasızdır ki bu normaldir. Sonrasındaki her şey MAC korumalı olmalıdır; bu yüzden MAC korumalı bir dosyaya tek bir düz artımlı güncelleme eklemek pmcfRequiredRevisionMissing ve suçlu revizyon diziniyle başarısız olur. Bir boşluğa göz yummak, saldırganın yalnızca bir kez daha kaydederek korumayı sıyırmasına izin verirdi. Zincir boyunca üç ek değişmez geçerlidir; her birinin kendi hata kodu vardır

  • pmcfKDFSaltChanged/KDFSalt değeri çapadan itibaren sabit kalmalıdır; çünkü dönen bir salt, sahtecinin anahtarları kendi seçtiği parametrelerle yeniden türetmesine izin verirdi
  • pmcfDigestDowngrade — özet gücü hemen önceki revizyon yerine doğrulanmış son MAC ile karşılaştırılır; böylece Modern profili altında SHA-384 ile başlayan bir zincir sessizce SHA-256 ile devam edemez
  • pmcfPermissionDowngrade — bir revizyon, önceki bir revizyonun kimliğini doğruladığı PDF MAC gereksinimini temizleyemez

İçselleştirmeye değer sonuç, geçmiş MAC değerlerinin artık etkin kuyruk olmadıktan sonra bile bağımsız olarak doğrulanmasıdır. Açılıştaki eski revizyonu düzenle, sonra yeni MAC ekle saldırısının ayakta kalamaması bundandır: en yeni MAC kendi başına doğrulanır, ValidatePDFMAC memnundur ve zincir yine de revizyon 1 üzerine pmcfRevisionInvalid ile iner

İmza sıralaması: önce kuyruk anahtarları, en son signatureDigest

MAC tek başına durmak yerine bir CMS imzasına bağlandığında yazım sırası üslup sorusu olmaktan çıkar. HotPDF, imza /ByteRange değeri hesaplanmadan önce /AuthCode, /KDFSalt, ISO 32004 geliştirici uzantısı ve /SigObjRef öğelerinin aynı revizyona yazılmasını ister; herhangi birini sonra eklerseniz o baytlar imzanın kapsadığı aralığın dışına düşer ve imzası doğrulanan ama MAC bağlaması imzasız bir dosya üretirsiniz. İki özet sonra ters yönde çalışır; ilk bakışta döngüsel görünür ama değildir. PDF MAC signatureDigest öğesi, CMS SignerInfo.signature OCTET STRING öğesinin ham içerik sekizlilerini bağlar — tüm CMS DER öğesini değil, imzalı öznitelikleri de değil — bu yüzden ham imza değeri var olduktan sonra kurulur ve id-attr-pdfMacData imzasız özniteliği olarak enjekte edilir. /Contents imza ByteRange dışında tutulduğundan ve imzasız öznitelikler imza hesaplamasına asla girmediğinden imzala, MAC kur, CMS sar dizisi kriptografik döngü olmadan temizce kapanır. İki sonuç çıkar: /ByteRange bekçisi ve /Contents yer tutucusu şifreli dosyada bile düz metin kalmalı ve nesne akışlarının dışında durmalıdır, yoksa sabit genişlikli yamalıcı onları bulamaz; MAC özeti de SHA-256 olduğunda imzalama özeti doğrudan yeniden kullanılır, aksi halde iki özet bağlamı da çıktı akışı üzerinde tek geçişte güncellenir

CMS imzasına bağlı PDF MAC için HotPDF yazım sırası: MAC anahtarları ByteRange ölçülmeden önce revizyona girer ve imza özeti sonradan ham SignerInfo imza sekizlilerinden kurulur
AuthCode, KDFSalt, SigObjRef ve geliştirici uzantısını ByteRange ölçülmeden önce yazmak, MAC bağlamasını imzanın kapsadığı aralığın içinde tutan şeydir
var
  Pdf: THotPDF;
  Options: THPDFPDFMACOptions;
  Info: THPDFPDFMACValidationInfo;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.AutoLaunch := False;
    Pdf.FileName := 'unsigned.pdf';
    Pdf.ActivateProtection := True;
    Pdf.CryptKeyLength := aesgcm;
    Pdf.OwnerPassword := 'owner';
    Pdf.UserPassword := 'user';
    Pdf.ProtectOptions := [prPrint, prExtractContent];
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 12);
    Pdf.CurrentPage.AddSignedSignatureField('Approval',
      Rect(72, 120, 280, 160), 16384);
    Pdf.EndDoc;

    Options := THPDFPDFMACOptions.Modern;      // SHA-384 belge özeti
    if THotPDF.SignPDFWithPFXAndAttachedPDFMAC('unsigned.pdf',
         'signed.pdf', 'signer.pfx', 'pfx-secret', 'user', Options) then
      if Pdf.ValidatePDFMAC('signed.pdf', 'user', Options, Info) then
      begin
        // Location = pmlAttachedToSignature ve iki özet
        // ayrı ayrı raporlanır
        Writeln('signature object  : ', Info.SignatureObjectNumber);
        Writeln('signature digest  : ', Info.SignatureDigestMatched);
        Writeln('full file coverage: ', Info.FullFileCoverage);
        Writeln('perms authentic   : ', Info.PermissionsAuthenticated);
      end;
  finally
    Pdf.Free;
  end;
end;

Doğrulama aynı yolu öbür uçtan geri izler: o an etkin klasik çapraz başvuru kuyruğundan doğrudan /AuthCode öğesini oku, nesil duyarlı dolaylı /SigObjRef öğesini izle, tek imza alanının /V öğesini bağladığını doğrula ve belge özeti hatasını imza özeti hatasından ayrı raporla. Bunlar farklı tanılardır ve tek bir boolean içinde birleştirmek, sayfa içeriğinin mi yoksa imza değerinin mi değiştirildiğini söyleyen tek bilgiyi atar. Zaten CMS işi yapıyorsanız bu, PAdES imzalama makalesi ve yüklenen belgelerde imza doğrulama kılavuzu ile yan yana durur

/P öğesine asla güvenmeyin: önce 16 baytlık /Perms şifresini çözün

ISO/TS 32004, «bu belge PDF MAC gerektirir» sinyalini 13. izin biti üzerinden verir ve onu okumanın bariz yolu yanlış olandır; çünkü şifreleme sözlüğündeki /P tamsayısı düz metindir ve kimliği doğrulanmamıştır — herkes o biti bir metin düzenleyicide çevirip gereksinimi düşürebilir. ISO 32000-2 §7.6 yanıtı /Perms girdisinde verir ve HotPDF onu kullanır: 16 baytlık /Perms dizgesini dosya şifreleme anahtarıyla AES-256 CBC, sıfır IV, dolgusuz çözün, sonra hiçbir şeye inanmadan önce düz metnin her alanını denetleyin. 1 ile 4 arası baytlar izin değerini little-endian sırada tutar ve /P tamsayısına tam olarak eşit olmalıdır; 5 ile 8 arası baytlar 0xFF değerindedir; 9. bayt T ya da F meta veri şifreleme bayrağıdır; 10 ile 12 arası baytlar adb değişmez işaretidir. Ancak bunların hepsi tuttuğunda PermissionsAuthenticated True olur ve 13. bit okunur; polaritesine dikkat edin, MAC gereksinimi 0x1000 biti temiz olduğunda bildirilir. /P ile çözülen izinler arasındaki uyumsuzluk günlüğe yazılıp geçilecek bir uyarı değildir; sahte bir izin kümesidir ve doğru yanıt kapalı başarısız olmaktır

HotPDF, PDF izinlerinin kimliğini on altı baytlık Perms dizgesini dosya şifreleme anahtarıyla çözerek ve 13. biti okumadan önce little-endian izin değerini, FF dolgu baytlarını, meta veri bayrağını ve adb işaretini denetleyerek doğrular
Düz metin /P tamsayısının kimliği doğrulanmamıştır; bu yüzden PDF MAC gereksinimi ancak çözülen /Perms öğesinin her alanı denetlendikten sonra okunur

Algoritma çevikliği özet düzeyinde durur

ISO/TS 32004 belge özetini seçmenize izin verir ve yalnızca belge özetini. HotPDF, kimlik doğrulama için HMAC-SHA-256, anahtar türetme için RFC 5869 uyarınca HKDF-SHA-256 ve anahtar sarma için RFC 3394 uyarınca AES-256 key wrap öğelerini sabit tutar; üzerinde pmdaSHA256 ile pmdaSHA3_512 arasında değişen bir THPDFPDFMACDigestAlgorithm durur. Doğal hata, «SHA3-512 profili» ifadesini HMAC öğesini de değiştirme lisansı saymaktır; bu, birlikte çalışabilir anlamda artık PDF MAC olmayan bir dosya üretir. Kendi doğrulayıcınızı yazıyorsanız kopyalamaya değer bir gerçekleme ayrıntısı: bayt aralığını karma yapmadan önce özet OID değerini CMS AuthenticatedData içinden okuyun; çünkü SHA-256 öğesini sabit kodlayıp sonra uzlaştırmak çevikliği bir etikete dönüştürür ve düşmanca bir dosyanın, algoritmanın hiç desteklenmediğini keşfetmeden önce size tüm belgeyi akıtmasına izin verir. CMSAlgorithmProtection, AuthenticatedData özet algoritması, bütünlük bilgisi messageDigest ve bayt aralığı özeti aynı algoritmayı adlandırmalıdır; herhangi bir uyuşmazlık kapalı başarısız olur

var
  Options: THPDFPDFMACOptions;
begin
  Options := THPDFPDFMACOptions.Compatibility;  // SHA-256, altısını da kabul eder
  Options := THPDFPDFMACOptions.Modern;         // SHA-384, 256 biti reddeder
  Options := THPDFPDFMACOptions.HighAssurance;  // yalnızca SHA3-512, AES-GCM

  // Özel bir profil yasaldır ama üretimde kullandığı algoritmanın
  // doğrulama izin listesinde de görünmesi gerekir; yoksa yapılandırma
  // tek bir bayt yazılmadan reddedilir
  Options.Profile := pmppCustom;
  Options.DigestAlgorithm := pmdaSHA512;
  Options.AllowedDigestAlgorithms := [pmdaSHA512, pmdaSHA3_512];
  Options.RequireAESGCM := True;
end;

PDF MAC neyi kanıtlar ve neyi kanıtlamaz

Doğrulanmış bir PDF MAC zinciri, korumalı her revizyonun dosya şifreleme anahtarını elinde tutan birinin yazdığıyla bayt bayt aynı olduğunu, hiçbir korumalı revizyonun kaldırılmadığını ya da yeniden sıralanmadığını ve çapadan sonra hiçbir korumasız revizyonun eklenmediğini kanıtlar — tam da düz AES-256 şifrelemenin açık bıraktığı saldırı sınıfı; çünkü gizlilik bütünlük hakkında hiçbir şey söylemez ve eklenmiş revizyonlu şifreli bir PDF, sağlamı kadar mutlu biçimde çözülür. Kanıtlamadığı şey yazarlıktır. MAC anahtarı dosya şifreleme anahtarından türetilir; bu yüzden belgeyi açabilen herkes, meşru her alıcı dahil, değiştirilmiş bir sürüm üzerinde geçerli bir MAC üretebilir. Simetrik bir temeldir ve simetrik temeller atıfta bulunamaz. Bir şeyi kimin değiştirdiğini bilmeniz gerekiyorsa arkasında sertifika olan bir dijital imza gerekir ve PDF MAC sonra onu, imzanın tek başına kapsamadığı artımlı yapıyı koruyarak tamamlar. İkisini katman olarak ele alın ve iki kararın tek bir durum simgesine birleştirilmesi yerine bağımsız raporlanmasına izin verin

Burada anlatılan PDF MAC giriş noktaları — AddStandalonePDFMAC, SignPDFWithPFXAndAttachedPDFMAC, ValidatePDFMAC ve ValidatePDFMACChain — Delphi ve C++Builder için standart HotPDF Delphi Component paketiyle gelir; ürün sayfası seçenekler kaydı, durum numaralandırmaları ve revizyon başına doğrulama dizisi için tam başvuruyu taşır