Teknik Makale

PDFium ile Delphi'de PAdES B-B ile PDF İmzalama

PDFium Bileşeni, SignPades yöntemi aracılığıyla bir PDF'i PAdES B-B dijital imzasıyla imzalar: belgeyi yükler, imzalanan bayt aralığını özetler (hash), bir CAdES CMS yapısı oluşturur ve imzayı artımlı bir güncelleme olarak ekler. Kriptografik arka uç yalnızca Windows'a özeldir, bu nedenle imzalamadan önce her çağrıyı PadesCryptoAvailable ile koruyun

Durum tanıdık. Masanıza bir sözleşme PDF'i gelir, hukuk departmanı dışarı çıkmadan önce bunun dijital olarak imzalanmasını ister ve siz belgeleri işlemek ve incelemek için zaten kullandığınız aynı PDFium derlemesine yönelirsiniz, ancak PDFium'un bir imza yazamayacağını keşfedersiniz. İmza API'si kesinlikle salt okunurdur. PDFium Bileşeni, özet işlevinden bayt düzeyindeki enjeksiyona kadar tüm imzalama işlem hattını Pascal'da barındırarak bu boşluğu kapatır ve bu makale bu işlem hattını uçtan uca inceler

PDFium neden dijital imza yazamıyor?

PDFium imzaları salt okunur nesneler olarak sunar ve imza oluşturan hiçbir şey sunmaz. FPDFSignatureObj_* ailesi mevcut bir imzayı listelemenize, /Contents değerini okumanıza ve /ByteRange aralığını incelemenize olanak tanır, ancak bir imza sözlüğü oluşturan, bir /Contents yuvası ayıran veya bir bayt aralığı yazan bir karşılığı yoktur; artımlı kaydetme mevcuttur (FPDF_INCREMENTAL ile FPDF_SaveAsCopy) ancak hiçbir imzalama kancası taşımaz. Bu nedenle PDFium'un üzerinde bir PDF imzalayan herhangi bir bileşenin her imza baytını kendisi üretmesi gerekir; bu da PDFium Bileşeni'nin bu mekanizmayı üç saf Pascal biriminden oluşturmasının nedenidir. FPC 3.2.2, md5 ve sha1'i gönderir ancak hiçbir SHA-2'yi göndermez ve Delphi System.Hash SHA-256 API'si FPC ile kaynak uyumlu değildir, bu nedenle FPdfSha256 hiçbir derleyici dallanması olmadan tüm CMS kod yollarını tek bir TSHA256Digest türünde tutan kendi başına bir FIPS 180-4 uygulamasıdır. FPdfAsn1, CMS yapılarının ihtiyaç duyduğu DER kodlayıcıyı ve okuyucuyu sağlar og FPdfCms her ikisinin üzerinde CAdES SignedData'yı birleştirir

Delphi'de bir PDF'i dijital olarak nasıl imzalarsınız?

Belgeyi yükleyin, ardından bir sertifika parmak izi (thumbprint) ile SignPades yöntemini çağırın. PDFium Bileşeni bu parmak izini Current User "MY" sertifika deposuna karşı çözümler, eşleşen sertifikayı ve özel anahtarını çeker ve adlandırdığınız yola imzalı bir kopyasını yazar

uses
  PDFium, FPdfCrypto;

procedure SignContract(const AThumbprint: string);
var
  Pdf: TPdf;
begin
  if not PadesCryptoAvailable then
    raise Exception.Create('PAdES signing requires the Windows CNG backend');

  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'contract.pdf';   // the document to sign
    Pdf.Active := True;
    // Second argument: SHA-1 thumbprint of a certificate in the Current
    // User "MY" store. First argument: destination for the signed copy.
    if not Pdf.SignPades('contract-signed.pdf', AThumbprint) then
      raise Exception.Create('Signing failed');
  finally
    Pdf.Free;
  end;
end;

PadesCryptoAvailable her zaman ilk kontrol ettiğiniz yerdir. Windows'ta True döndürür ve crypt32/ncrypt arka ucu etkindir; başka herhangi bir platformda False döndürür ve bir imzalama çağrısı EPadesCrypto hatası verir. Korumayı zorunlu kılmak, bir Linux veya macOS derlemesinin orada çalışamayacak bir yolda çalışma zamanında hata vermesini engeller. Parmak izinin kendisi sertifikanın SHA-1 özetidir (Windows sertifika yöneticisinin Ayrıntılar sekmesinde gösterdiği değerin aynısı) ve kaynak kodunuza asla anahtar materyali yerleştirmeden belirli bir imzalayanı adlandırır

CMS'ye ne girer: imzalı öznitelikler ve RFC 5652

Bir PAdES temel imzası dosya üzerinde ham bir RSA imzası değildir; zorunlu imzalı öznitelikleri taşıyan bir CAdES CMS SignedData yapısıdır ve FPdfCms.BuildSignedData tam olarak bu kümeyi yayar: content-type, message-digest ve imza sertifikasını imzalayana özetle bağlayan ESS özniteliği olan signing-certificate-v2. Buradaki bir ayrıntı, neredeyse her el yapımı CMS uygulamasını bozar. RFC 5652 §5.4, imzalı öznitelikler özetinin DER SET OF kodlaması (etiket 0x31) üzerinden hesaplanmasını gerektirirken, aynı öznitelikler SignerInfo içinde IMPLICIT [0] etiketi (0xA0) altında seyahat eder. PDFium Bileşeni öznitelik kümesini bir kez kodlar, 0x31 formunu özetler, ardından ağaç üzerinde ikinci bir geçiş yapmadan yayım için yalnızca önde gelen etiket baytını 0xA0 olarak yeniden yazar; böylece tek bir arabellek her iki role de hizmet eder

var
  Pdf: TPdf;
  Opts: TPadesSignOptions;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'contract.pdf';
    Pdf.Active := True;

    Opts := TPadesSignOptions.Default;
    Opts.CertificateThumbprint := 'a1b2c3d4e5f6...';  // signer in the MY store
    Opts.Reason := 'I approve this agreement';
    Opts.Location := 'Berlin, DE';
    Opts.ContentsSize := 16384;                        // hex width of /Contents

    if not Pdf.SignPades('contract-signed.pdf', Opts) then
      raise Exception.Create('Signing failed');
  finally
    Pdf.Free;
  end;
end;

Seçenekler aşırı yüklemesi, ISO 32000-1 §12.8.1'in tanımladığı imza sözlüğü meta verilerini ekler: Reason, Location, ContactInfo ve Name; hepsi isteğe bağlıdır ve hepsi imza değeri sözlüğüne yazılır. Bir kısıtlamaya takılmak kolaydır. CAdES commitment-type-indication imzalı özniteliği eklemek için CommitmentTypeOid özelliğini ayarlarsanız, Reason özelliğini de ayarlamayın; ETSI EN 319 142-1 §6.3 her ikisini birden taşımayı yasaklar, çünkü ikisi aynı amacı farklı yollarla ifade eder

ByteRange ve /Contents yuvası birbirine nasıl sığar?

Bir imza, imzanın kendisini tutan baytlar hariç tüm dosyayı kapsamak zorundadır og PAdES bu döngüselliği SignPadesBytes'ın kesin olarak yönettiği sabit genişlikli bir yer tutucu ile çözer. ContentsSize baytlık bir /Contents hex dizesi rezerve eder (varsayılan olarak 16384, tipik bir CMS SignedData'dan belirgin şekilde daha büyüktür), tam yuva farkını konumlandırmak için artımlı güncellemeyi serileştirir, ardından /ByteRange aralığını yuvayı parantez içine alan iki aralık olarak hesaplar; hex dizesinin açılış sınırlayıcısından önceki her şey og kapanış sınırlayıcısından sonraki her şey. SHA-256 yalnızca bu iki aralık üzerinde çalışır. Bitmiş CMS rezerve edilen yuvaya hex olarak kodlanır, sabit genişliğe göre sıfırla doldurulur ve çapraz referans güncellemesi eklenir. Genişlik önceden sabitlendiğinden, yuvayı doldurmak takip eden hiçbir baytı kaydırmaz, bu da bayt aralığının geçerli kalmasının tek nedenidir; orijinal belge baytları olduğu gibi korunur, böylece aynı dosya üzerindeki daha önceki bir imza, ISO 32000-1 §12.8.1 artımlı imzalamanın öngördüğü şekilde bozulmadan kalır

Windows CNG arka ucu ve sınırları

PDFium Bileşeni yalnızca Windows'ta imzalar ve bu sınır bilinçlidir. FPdfCryptoWin, derleme zamanı DLL bağımlılığı eklemeden crypt32.dll ve ncrypt.dll dosyalarını dinamik olarak bağlar ve imzalama zinciri standart CNG'dir: MY deposunu açın, sertifikayı özetine göre bulun, CryptAcquireCertificatePrivateKey aracılığıyla özel anahtar tanıtıcısını edinin ve NCryptSignHash işlevini çağırın. PKCS#1 v1.5 ile RSA, RSA-PSS ve ECDSA desteklenir. ECDSA, diğerlerinin ihtiyaç duymadığı bir düzeltmeye ihtiyaç duyar; çünkü NCryptSignHash ham IEEE P1363 r-ve-s çiftini döndürürken, CMS bir DER ECDSA-Sig-Value SEQUENCE bekler, bu nedenle arka uç bunu RFC 5480 uyarınca yeniden kodlar

var
  Pdf: TPdf;
  Opts: TPadesSignOptions;
  Output: TFileStream;
begin
  if not PadesCryptoAvailable then
    Exit;   // no signing backend on this platform

  Opts := TPadesSignOptions.Default;
  Opts.CertificateThumbprint := ReadThumbprintFromConfig;

  Pdf := TPdf.Create(nil);
  Output := TFileStream.Create('contract-signed.pdf', fmCreate);
  try
    Pdf.FileName := 'contract.pdf';
    Pdf.Active := True;
    Pdf.SignPadesToStream(Output, Opts);
  finally
    Output.Free;
    Pdf.Free;
  end;
end;

Bunun pratik sonucu, özel anahtarın Windows sertifika deposunda yaşaması gerektiğidir. Bir PFX dosyasında tutulan bir sertifika yalnızca onu Current User deposuna aktardıktan sonra çalışır, bu noktada parmak izi SignPades'e geçirdiğiniz değerdir. Bu sürümde PKCS#11 veya HSM yolu ve yazılım anahtarı dosyası arka ucu yoktur, bu nedenle PadesCryptoAvailable False döndürdüğünde o makinede imzalama yapılamaz

PAdES B-B'nin durduğu yer

PAdES B-B temel seviyedir, dört PAdES seviyesinin tabanıdır: kimin imzaladığını ve o zamandan beri baytların değişmediğini kanıtlar, bundan fazlasını değil. Bir B-B imzası güvenilir bir zaman damgası taşımaz, bu nedenle imzalamanın ne zaman gerçekleştiğini kanıtlayamaz ve iptal verisi gömmez; bu nedenle yıllar sonraki bir doğrulayıcı sertifika zincirini ve durumunu kendisi getirmelidir. Bu boşluklar tam olarak üst seviyelerin kapattığı yerlerdir. Bir denetçinin kabul edeceği bir imzalama zamanına ihtiyaç duyduğunuzda, uzun vadeli doğrulama için bir RFC 3161 zaman damgası ve DSS eklemek imzayı B-T ve üzerine taşır; bitmiş bir imzayı okuyup hangi seviyeye ulaştığını onaylamak istediğinizde, bir PDF imzasını ve PAdES seviyesini incelemek yardımcı araçtır; ve hiçbir şeyi imzalamadan önce, bir PDF'i güvenlik risklerine karşı denetlemek neye imza atacağınızı söyler

Burada gösterilen SignPades yöntemleri, PDFium'un kutudan çıkardığı salt okunur imza incelemesiyle birlikte Delphi ve C++Builder için PDFium Bileşeni ile birlikte gelir