Artikel Teknis

Tanda Tangan Digital PAdES di Delphi: Penandatanganan dan Validasi dengan PDF Library for Delphi

Memvalidasi satu tanda tangan PAdES berarti memeriksa tiga hal yang saling bebas, dan centang hijau di sebuah viewer hanya bercerita tentang yang ketiga. Pertama, larik /ByteRange harus mencakup byte yang tepat: rentang yang disebutnya wajib merekonstruksi persis masukan yang dipakai menghitung digest CMS, tanpa menyisakan byte tertandatangani di luarnya. Kedua, sertifikat di dalam CMS harus berantai ke root yang Anda percayai dan membawa atribut signing-certificate tertandatangani yang disyaratkan PAdES. Ketiga, jika profilnya mengklaim adanya timestamp, sebuah token RFC 3161 harus mengikat nilai tanda tangan ke satu titik waktu sebelum sertifikatnya kedaluwarsa. Acrobat meruntuhkan ketiganya menjadi satu ikon; pemeriksa konformansi menjaganya tetap terpisah, dan begitu pula seharusnya kode yang menghasilkan berkas-berkas ini. losLab PDF Library (PDF Library for Delphi) memberi Anda sisi penandatanganannya, penanaman ulang timestamp, dan pemanggilan audit untuk memeriksa sebuah ByteRange sebelum Anda memercayainya

Satu pembedaan menjegal hampir setiap implementasi PAdES pertama, jadi ia layak dinyatakan sebelum kode apa pun. Tanda tangan yang ditulis dengan /SubFilter /adbe.pkcs7.detached adalah tanda tangan ISO 32000-1 §12.8 yang sepenuhnya sehat dan akan dilaporkan Acrobat sebagai valid. Ia juga bukan tanda tangan PAdES, karena ETSI EN 319 142-1 mensyaratkan ETSI.CAdES.detached pada setiap tingkat baseline. Pemeriksa konformansi eIDAS menolak yang pertama dan menerima yang kedua walaupun kriptografinya identik. Profil adalah klaim yang dibuat dokumen tentang dirinya sendiri, dan membuat klaim itu benar hanyalah satu pemanggilan di PDF Library for Delphi

Apa yang mengubah tanda tangan PDF menjadi tanda tangan PAdES

ETSI EN 319 142-1 mendefinisikan empat tingkat baseline yang bertumpuk di atas format CMS. PAdES-B-B adalah titik masuknya: sebuah tanda tangan CAdES di dalam field tanda tangan PDF dengan SubFilter ETSI.CAdES.detached dan atribut signing-certificate tertandatangani. PAdES-B-T menambahkan timestamp RFC 3161 atas nilai tanda tangan, membuktikan tanda tangan itu sudah ada sebelum satu titik waktu yang tak bisa dimundurkan siapa pun. PAdES-B-LT menanamkan sertifikat, CRL, dan respons OCSP yang diperlukan untuk validasi ke dalam sebuah Document Security Store, sehingga berkasnya tetap dapat diverifikasi setelah CA penerbit memensiunkan infrastrukturnya. PAdES-B-LTA memuncaki tumpukan itu dengan document timestamp yang melindungi ulang bukti yang sudah terkumpul seiring melemahnya algoritma

PDF Library for Delphi memetakan konsep-konsep ini ke API sign-process-nya. Penanda profilnya adalah SetSignProcessCustomSubFilter. Jika kebijakan Anda memerlukan indikasi jenis komitmen (bukti asal, bukti persetujuan, atau salah satu pengidentifikasi ETSI bernomor 1 sampai 6), itu lewat SetSignProcessCommitmentType. Kebijakan tanda tangan eksplisit dilekatkan dengan SetSignProcessSignaturePolicy, yang menerima OID kebijakan beserta digest-nya. Satu default patut diperhatikan: dengan algoritma digest dibiarkan otomatis, pustaka memilih SHA-256 untuk tanda tangan ETSI dan adbe.pkcs7.detached, dan hanya jatuh ke SHA-1 pada jalur lawas adbe.pkcs7.sha1. Tetapkan saja secara eksplisit. Auditor menanyakan hash apa yang Anda pakai, dan nilai eksplisit di dalam kode lebih mudah dipertahankan daripada default yang harus Anda cari dulu di manual untuk dijelaskan

Tangga level baseline PAdES B-B, B-T, B-LT, dan B-LTA yang dibangun dengan PDF Library for Delphi, menunjukkan bagaimana setiap level menambahkan timestamp, bukti DSS, atau document timestamp yang dapat diperbarui di atas inti ETSI.CAdES.detached
Setiap level baseline ETSI menumpuk satu jaminan lagi di atas inti CAdES yang sama, dari atribut bertanda tangan hingga document timestamp yang dapat diperbarui

Menghasilkan tanda tangan baseline

API datar menjalankan penandatanganan sebagai mesin keadaan sekali jalan: buka proses pada berkas sumber, konfigurasikan, selesaikan ke berkas keluaran, baca kode hasilnya. Rangkaian di bawah menghasilkan tanda tangan PAdES-B-B dengan SHA-256. Baris yang paling menentukan justru tidak berhubungan dengan tanda tangannya sendiri. Baris itu adalah cadangan /Contents yang sengaja dilebihkan, sebab itulah satu hal yang tidak bisa Anda ubah nanti bila suatu saat sebuah timestamp harus ditambahkan ke tanda tangan ini

var
  Pdf: TPDFlib;
  SignId: Integer;
begin
  Pdf := TPDFlib.Create;
  try
    SignId := Pdf.NewSignProcessFromFile('invoice.pdf', '');
    if SignId = 0 then
      raise Exception.Create('cannot open source PDF');
    Pdf.SetSignProcessField(SignId, 'Sig1');
    Pdf.SetSignProcessPFXFromFile(SignId, 'company.pfx', PfxPassword);
    Pdf.SetSignProcessInfo(SignId, 'Approved', 'Vienna', 'billing@example.com');
    Pdf.SetSignProcessCustomSubFilter(SignId, 'ETSI.CAdES.detached');
    Pdf.SetSignProcessDigestAlgorithm(SignId, 2);          // SHA-256
    Pdf.SetSignProcessReserveContentsBytes(SignId, 8192);  // ruang untuk timestamp nanti
    Pdf.EndSignProcessToFile(SignId, 'invoice-signed.pdf');
    if Pdf.GetSignProcessResult(SignId) <> 1 then
      raise Exception.CreateFmt('signing failed, code %d',
        [Pdf.GetSignProcessResult(SignId)]);
    Pdf.ReleaseSignProcess(SignId);
  finally
    Pdf.Free;
  end;
end;

NewSignProcessFromFile mengembalikan 0 ketika sumbernya sama sekali tidak dapat dibuka. Sesudah itu, GetSignProcessResult memisahkan mode kegagalan yang benar-benar terjadi di produksi: 4 berarti kata sandi PDF salah, 7 kata sandi PFX salah, 9 berkas sertifikat tanpa private key, 10 path keluaran yang tak bisa ditulisi, 11 kegagalan saat byte tanda tangan sedang diterapkan. Mencatat kode numeriknya di samping nama berkas masukan mengubah tiket dukungan yang samar menjadi diagnosis satu menit

Menambahkan timestamp RFC 3161 yang tidak akan diambilkan pustaka untuk Anda

PDF Library for Delphi tidak menyertakan klien TSA, dan itu batas yang disengaja, bukan celah. Pustaka menghitung hash yang harus ditandatangani balik oleh otoritas timestamp lalu menanamkan ulang CMS yang sudah diperkaya sesudahnya; pertukaran HTTP dan bedah CMS di antaranya adalah milik pemanggil. Ada alasan teknis keras di balik pemisahan itu. Kontrol Windows CryptoAPI yang secara nominal menambahkan atribut tak tertandatangani, CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR, gagal dengan CRYPT_E_INVALID_INDEX pada tata letak SignedData terpisah yang dipakai PAdES. Jadi CMS yang diperkaya itu harus datang dari encoder CMS di bawah kendali Anda sendiri. Tidak ada pustaka yang bisa diam-diam melipat tokennya lewat satu pemanggilan sistem, dan pustaka mana pun yang mengaku bisa sedang membedah di suatu tempat yang tak dapat Anda lihat

Pipeline untuk menambahkan timestamp RFC 3161 ke tanda tangan PAdES di Delphi, memisahkan hashing dan penyematan PDF Library for Delphi dari permintaan TSA pemanggil dan re-enkoding CMS di dalam ruang /Contents yang dicadangkan
Library meng-hash dan menyematkan ulang sementara kode Anda mengambil token dan melakukan bedah CMS, dan hasilnya harus mendarat di dalam reservasi /Contents 8192 byte
var
  Pdf: TPDFlib;
  StsId: Integer;
  HashHex, TstDer, TsAttr, AugmentedCms: AnsiString;
begin
  Pdf := TPDFlib.Create;
  try
    StsId := Pdf.NewPAdESSignatureTimeStampProcessFromFile('invoice-signed.pdf', '');
    Pdf.SetPAdESSignatureTimeStampField(StsId, 'Sig1');
    Pdf.SetPAdESSignatureTimeStampDigestAlgorithm(StsId, 2);
    HashHex := Pdf.GetPAdESSignatureValueHashHex(StsId);
    // kedua pemanggilan di bawah adalah kode aplikasi: satu HTTP POST ke TSA Anda,
    // dan satu pengodean ulang CMS yang melekatkan token sebagai atribut tak tertandatangani
    TstDer := RequestTimeStampToken(HashHex);
    TsAttr := Pdf.BuildPAdESSignatureTimeStampAttribute(TstDer);
    AugmentedCms := AttachUnsignedAttribute(Pdf.GetPAdESSignatureCMSBytes(StsId), TsAttr);
    Pdf.SetPAdESSignatureCMSBytes(StsId, AugmentedCms);
    Pdf.EndPAdESSignatureTimeStampProcessToFile(StsId, 'invoice-bt.pdf');
    if Pdf.GetPAdESSignatureTimeStampProcessResult(StsId) <> 1 then
      raise Exception.Create('timestamp embedding failed');
    Pdf.ReleasePAdESSignatureTimeStampProcess(StsId);
  finally
    Pdf.Free;
  end;
end;

Perhatikan kode hasil di sini: 12 berarti field tanda tangan yang disebut tidak ada, 11 berarti CMS yang ada tidak dapat diurai, dan 13 berarti CMS yang sudah diperkaya tidak lagi muat di placeholder /Contents yang dicadangkan. Kode 13 yang paling menyakitkan, karena satu-satunya perbaikan adalah menandatangani ulang: token timestamp biasa beserta rantai sertifikatnya memakan 4 sampai 6 KB, dan cadangan 8192 byte yang dibuat pada langkah B-B ada justru agar langkah ini punya tempat mendarat

Validasi bermula di ByteRange, bukan di rantai sertifikat

Centang hijau di sebuah viewer adalah keputusan kepercayaan terhadap penyimpanan sertifikat mesin itu, bukan vonis struktural tentang berkasnya. Validasi terprogram sebaiknya bermula lebih rendah, dengan pertanyaan yang dibikin halus oleh incremental update: byte mana yang sebenarnya dicakup setiap tanda tangan? Setiap peningkatan yang dibahas di sini, entah tanda tangan kedua, kamus DSS, atau document timestamp, tiba lewat incremental update, dan tiap pembaruan menambahkan byte di luar /ByteRange tanda tangan sebelumnya. Byte tambahan itu sah. Validator tetap harus mengklasifikasikannya terhadap kebijakan modifikasi dokumen, dan tingkat DocMDP per field tempat kebijakan itu tinggal dapat dibaca dengan GetSignatureDocMDPLevelByName

Audit tata letak byte PDF bertanda tangan di Delphi yang menunjukkan span yang tercakup ByteRange, byte /Contents yang dikecualikan, pembaruan inkremental yang ditambahkan di luar rentang, dan putusan cakupan terhadap ukuran berkas
Dua span tercakup dengan byte milik tanda tangan sendiri dikecualikan menceritakan kisah cakupan yang sesungguhnya, dan update yang ditambahkan diklasifikasikan terhadap kebijakan DocMDP alih-alih ditakuti
var
  Doc: TPDFlibSignDoc;
  Names: TStringList;
  I: Integer;
  B0, B1, B2, B3, FileSize: Int64;
begin
  FileSize := TFile.GetSize('invoice-bt.pdf');  // sebelum Open: SignDoc memegang share lock
  Doc := TPDFlibSignDoc.Create;
  try
    if not Doc.Open('invoice-bt.pdf', '', False) then
      raise Exception.Create('cannot open for audit');
    Names := TStringList.Create;
    try
      Doc.GetSignatureFieldNames(Names);
      for I := 0 to Names.Count - 1 do
        if Doc.GetSignatureValueObjNum(Names[I]) > 0 then   // >0 berarti benar-benar ditandatangani
        begin
          B0 := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 11)));
          B1 := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 12)));
          B2 := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 13)));
          B3 := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 14)));
          if (B0 = 0) and (B2 + B3 = FileSize) then
            Writeln(Names[I], ': covers the file to EOF')
          else
            Writeln(Names[I], ': earlier revision, or unexpected ByteRange layout');
        end;
    finally
      Names.Free;
    end;
    Doc.Close;
  finally
    Doc.Free;
  end;
end;

Dua jebakan tinggal di jalur audit ini. TPDFlibSignDoc.Open memegang berkas dengan share lock eksklusif, sehingga validator yang juga ingin menghitung hash byte mentah berkas untuk verifikasi CMS harus membaca berkas itu ke memori sebelum membukanya untuk audit. Balik urutannya dan pembacaan gagal karena kunci yang Anda pasang sendiri. Jebakan kedua bersifat senyap, bukan berisik: padanan di API datar, GetSignProcessByteRange, mengembalikan Integer padahal offset di baliknya bertipe Int64, sehingga di atas 2 GB pemanggilan datar itu memotong tanpa mengeluh, dan itulah sebabnya contoh ini menarik offset lewat kelas audit. Satu ketiadaan juga layak disebut. Lapisan datar sama sekali tidak punya pembungkus VerifySignature. Vonis kriptografis datang dari TPDFlibSignatureVerifier di tingkat kelas, yang mengembalikan vsValid, vsInvalid, atau vsUnknown, atau dari validator eksternal yang sudah dipercaya kebijakan kepatuhan Anda

Validasi jangka panjang: DSS, VRI, dan document timestamp

PAdES-B-LT ada karena infrastruktur pencabutan itu fana. ETSI EN 319 142-1 §5.4.2.2 menetapkan Document Security Store: sebuah kamus tingkat dokumen yang membawa sertifikat, CRL, dan respons OCSP, secara opsional diindeks per tanda tangan lewat entri VRI yang berkunci hash dari /Contents tiap tanda tangan. Alur PDF Library for Delphi mencerminkan rancangan timestamp-nya. NewPAdESDSSProcessFromFile membuka prosesnya; AddPAdESDSSCertificate, AddPAdESDSSCRL, dan AddPAdESDSSOCSP menerima blob DER; AddPAdESDSSVRI mengikat materi terpilih ke satu tanda tangan; EndPAdESDSSProcessToFile menulis semuanya sebagai incremental update. Bagian sulitnya tetap di pihak Anda. Mengambil materi pencabutan, dan menimbang apakah ia cukup segar untuk layak ditanamkan, adalah tugas pemanggil. Pustaka menjamin kamus-kamusnya konforman secara struktural; ia tidak bisa menjamin responder OCSP Anda berkata jujur

Titik akhir arsip, B-LTA, menambahkan document timestamp: sebuah field tanda tangan terpisah yang tipenya DocTimeStamp alih-alih Sig, dihasilkan lewat SetSignProcessDocTimeStamp dengan panjang tanda tangan yang dicadangkan. Ia tidak menggantikan timestamp tanda tangan dari langkah B-T. Timestamp tanda tangan membuktikan kapan satu tanda tangan tertentu ada; document timestamp melindungi seluruh berkas, termasuk bukti DSS, dan ialah elemen yang diperbarui arsip jangka panjang setiap beberapa tahun seiring melemahnya algoritma. Profil arsip yang matang membawa keduanya. Untuk pembaca yang lebih tua daripada struktur-struktur ini, TPDFlibSignDoc.EnsurePAdESExtensions mencatatkan ekstensi pengembang ESIC di katalog dokumen, mengumumkan bahwa berkas itu memakai fitur yang didefinisikan ETSI

Satu reaksi atas semua ini layak dicegat lebih dulu, sebab ia tampak seperti bug padahal bukan. Sebuah viewer kerap melaporkan "validity unknown" pada berkas yang struktur PAdES-nya sepenuhnya benar. Kepercayaan dan struktur adalah dua sumbu yang saling bebas. Viewer itu sekadar tidak dapat merantai penanda tangan ke root yang ia percayai di mesin tersebut, hal yang lumrah dengan CA privat dan sertifikat uji, bahkan ketika audit ByteRange dan verifikasi CMS sama-sama lolos. Perbaikannya adalah mendistribusikan sertifikat root dengan benar, atau mengevaluasi terhadap trusted list Uni Eropa bila status eIDAS berkualifikasi memang tujuan sesungguhnya, bukan mengutak-atik kode penandatanganan

Untuk perspektif sisi audit, yakni mendaftar field tanda tangan di seluruh korpus, membuang tata letak ByteRange, dan membaca tingkat DocMDP secara massal, lihat tulisan pendamping tentang workbench kepatuhan dan penandatanganan. Dokumen tertandatangani yang juga harus memenuhi kebijakan arsip berada di alur kerja yang diuraikan dalam preflight PDF/A dan PDF/UA di Delphi. Dokumentasi API lengkap dan unduhan evaluasi ada di halaman produk losLab PDF Library for Delphi