Artikel Teknis

Memeriksa Tanda Tangan PDF dan Tingkat PAdES di Delphi

Anda telah menerima PDF yang ditandatangani dan Anda perlu menampilkan, di penampil Anda, siapa yang menandatanganinya, kapan ditandatangani, apakah tanda tangan tersebut mencakup seluruh file, dan seberapa jauh tanda tangan tersebut memenuhi kepatuhan jangka panjang. PDFium Component untuk Delphi dan Lazarus menjawab keempat pertanyaan tersebut dengan panggilan hanya-baca (read-only): FPDF_GetSignatureCount dan keluarga FPDFSignatureObj_* mengekspos kamus tanda tangan, dan TPdf.ValidatePades mengklasifikasikan tingkat dasar PAdES. Ini adalah artikel pertama dari tiga artikel tentang tanda tangan PDF dengan PDFium; dua artikel berikutnya membahas pembuatan tanda tangan B-B dan penambahan stempel waktu jangka panjang. Namun, satu batasan perlu dinyatakan di awal: semua hal di sini adalah pemeriksaan, dan membaca apa yang diklaim oleh tanda tangan adalah pekerjaan yang berbeda dari memverifikasi kriptografinya atau memutuskan apakah Anda mempercayai penandatangan

Mengapa kamus tanda tangan PDF bukan sekadar blob byte biasa

Tanda tangan PDF adalah sebuah kamus, bukan lampiran yang buram, dan dua entri terpentingnya memberi tahu Anda seberapa banyak bagian file yang sebenarnya dilindungi. ISO 32000-1 §12.8 mendefinisikan kamus tanda tangan dengan entri /ByteRange dan /Contents. /Contents menampung struktur CMS SignedData berkode heksadesimal (RFC 5652), selubung kriptografi yang membawa sertifikat penandatangan, atribut yang ditandatangani, dan nilai tanda tangan itu sendiri. /ByteRange adalah bagian yang sering diremehkan oleh pengembang: ia berupa array dari dua rentang offset-panjang yang secara bersama-sama mencakup seluruh file kecuali string heksadesimal /Contents. Celah itulah yang menjadi tempat byte tanda tangan berada, dan kedua rentang di kedua sisi adalah apa yang secara tepat dilindungi oleh tanda tangan tersebut

Desain ByteRange adalah apa yang membuat penyimpanan bertahap (incremental save) dapat diaudit. Karena penandatangan tidak dapat membuat hash byte tanda tangan yang belum ada, file dibagi di sekitar placeholder /Contents dan semua hal lainnya di-hash ke dalam tanda tangan. Tanda tangan yang ByteRange-nya tidak mencapai akhir file adalah tanda peringatan: konten yang ditambahkan setelah rentang yang dicakup, melalui pembaruan bertahap berikutnya, tidak akan merusak tanda tangan meskipun konten tersebut mengubah apa yang dilihat pembaca. Jadi hal pertama yang diperiksa oleh pemeriksa yang serius bukanlah siapa yang menandatangani, melainkan apakah tanda tangan tersebut mencakup byte yang tampak disahkannya

Membaca kamus tanda tangan dengan API hanya-baca PDFium

Komponen PDFium menampilkan kamus tanda tangan melalui dua anggota hanya-baca: SignatureCount dan rekaman Signature[Index]. Di balik layar, mereka memanggil FPDF_GetSignatureCount, FPDF_GetSignatureObject, and aksesor FPDFSignatureObj_* untuk /SubFilter, /ByteRange, /Contents, /Reason, dan waktu penandatanganan. Hanya-baca (read-only) adalah frasa kuncinya di sini: PDFium dapat mencantumkan dan membaca tanda tangan tetapi tidak memiliki API untuk membuat atau menulisnya, itulah sebabnya sisi penandatanganan dari seri artikel ini diimplementasikan oleh pustaka itu sendiri dan bukan oleh PDFium

var
  Pdf: TPdf;
  i: Integer;
  Sig: TPdfSignature;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'contract-signed.pdf';
    Pdf.Active := True;
    for i := 0 to Pdf.SignatureCount - 1 do
    begin
      Sig := Pdf.Signature[i];
      Writeln('SubFilter : ', Sig.Encoding);        // ETSI.CAdES.detached, adbe.pkcs7.detached, ...
      Writeln('Signed at : ', Sig.Time);            // signer date string, e.g. D:20260708120000+02'00'
      Writeln('Reason    : ', Sig.Reason);
      Writeln('CMS length: ', Length(Sig.Content)); // raw DER SignedData taken from /Contents
      Writeln('DocMDP    : ', Sig.Permission);      // 0 = no certification, 1..3 = MDP level
    end;
  finally
    Pdf.Free;
  end;
end;

Setiap rekaman TPdfSignature memetakan secara langsung ke kamus. Encoding adalah /SubFilter, bidang tunggal yang paling diagnostik, karena ia menamai handler tanda tangan dan segera memisahkan tanda tangan ETSI.CAdES.detached modern dari tanda tangan lama atau tanda tangan yang dilarang. Time adalah waktu penandatanganan yang dinyatakan oleh penulis sebagai string tanggal PDF, yang merupakan klaim oleh penandatangan dan bukan waktu tepercaya. Content adalah CMS SignedData mentah, dan Permission mengekspos tingkat sertifikasi DocMDP (0 untuk tanda tangan persetujuan biasa, 1 hingga 3 untuk tanda tangan sertifikasi yang mengunci perubahan di kemudian hari). Satu bidang yang tidak ditampilkan oleh rekaman adalah ByteRange yang diurai, dan kelalaian itu disengaja, karena ValidatePades melakukan aritmatika cakupan ByteRange untuk Anda alih-alih memaksa Anda melakukannya kembali secara manual

Apa perbedaan antara PAdES B-B, B-T, B-LT, dan B-LTA?

Keempat tingkat dasar PAdES membentuk tangga dari tanda tangan yang valid secara minimal hingga tanda tangan yang dibangun untuk bertahan selama beberapa dekade pengarsipan, dan setiap tingkat secara ketat mencakup tingkat di bawahnya. ETSI EN 319 142-1 mendefinisikannya sebagai B-B, B-T, B-LT, dan B-LTA. B-B (Basic) adalah tanda tangan ditambah atribut bertanda tangan wajib dan tidak lebih. B-T (Timestamp) menambahkan stempel waktu RFC 3161 tepercaya di atas tanda tangan, sehingga momen penandatanganan dibuktikan oleh otoritas stempel waktu (time-stamping authority) alih-alih ditegaskan oleh jam penandatangan. B-LT (Long-Term) menyematkan materi validasi — rantai sertifikat, dan secara opsional respons OCSP atau CRL – di dalam file, sehingga tanda tangan masih dapat divalidasi bertahun-tahun kemudian ketika infrastruktur penerbit sudah tidak ada. B-LTA (Long-Term dengan stempel waktu Arsip) membungkus stempel waktu dokumen di sekitar materi tersebut, melindungi data jangka panjang itu sendiri dan memberi Anda titik untuk menstempel ulang sebelum kriptografi yang mendasarinya menjadi usang

Penafsiran praktisnya adalah tentang jangka waktu. Tanda tangan B-B menjawab "apakah seseorang menandatangani ini". B-T menjawab "dan kapan, secara terbukti". B-LT menjawab "dan apakah saya masih bisa memeriksanya setelah sertifikat kedaluwarsa". B-LTA menjawab "dan apakah pemeriksaan itu masih berlaku dalam dua puluh tahun". Profil peraturan memilih anak tangga: banyak konteks e-invoicing dan eIDAS memerlukan setidaknya B-T, dan mandat pengarsipan menjangkau B-LT atau B-LTA. Mengetahui anak tangga mana yang benar-benar dicapai oleh dokumen, sebelum Anda menerima atau menolaknya, is the whole point of the inspection step

Mendeteksi tingkat dasar dengan TPdf.ValidatePades

Komponen PDFium mengurangi seluruh pertanyaan tingkat menjadi satu panggilan tunggal. TPdf.ValidatePades mengembalikan rekaman TPadesValidationResult yang bidang Level-nya berupa TPadesLevelplNone, plUnknown, plB_B, plB_T, plB_LT atau plB_LTA — bersama dengan kumpulan masalah (issues), jumlah tanda tangan, dan jumlah stempel waktu dokumen. Tingkat disimpulkan secara monoton: validator menetapkan B-B terlebih dahulu, kemudian mempromosikannya ke B-T jika stempel waktu tanda tangan atau stempel waktu dokumen ada, ke B-LT jika katalog membawa /DSS dengan sertifikat dan penanda /Extensions /ESIC Tingkat 1, dan ke B-LTA jika stempel waktu dokumen dan penanda ESIC Tingkat 2 keduanya ada. Dua pembantu membuat hasilnya dapat ditindaklanjuti: IsCompliant bernilai True hanya ketika tingkat mencapai setidaknya B-B dan kumpulan masalah kosong, dan IsCompliantAt memungkinkan Anda menegaskan batas bawah kebijakan seperti plB_T

var
  Pdf: TPdf;
  R: TPadesValidationResult;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'contract-signed.pdf';
    Pdf.Active := True;
    R := Pdf.ValidatePades;
    case R.Level of
      plNone:    Writeln('No PAdES signature present');
      plUnknown: Writeln('Signature present but level undeterminable');
      plB_B:     Writeln('PAdES B-B   (basic)');
      plB_T:     Writeln('PAdES B-T   (trusted timestamp)');
      plB_LT:    Writeln('PAdES B-LT  (long-term material embedded)');
      plB_LTA:   Writeln('PAdES B-LTA (archive timestamp)');
    end;
    Writeln('Signatures   : ', R.SignatureCount);
    Writeln('DocTimeStamps: ', R.DocTimeStampCount);
    if R.IsCompliantAt(plB_T) then
      Writeln('Meets the B-T policy floor')
    else
      Writeln('Below the required B-T level');
  finally
    Pdf.Free;
  end;
end;

Mengapa adbe.pkcs7.sha1 menjadi SubFilter yang dilarang?

Karena SHA-1 telah rusak dan handler adbe.pkcs7.sha1 menyertakannya di dalam. SubFilter tersebut melakukan pra-hash dokumen dengan SHA-1 sebelum membungkusnya dalam PKCS#7, dan SHA-1 telah rentan terhadap tabrakan (collision) selama bertahun-tahun, sehingga klausul 6.3 EN 319 142-1 melarangnya secara langsung untuk tanda tangan tingkat dasar. ValidatePades memunculkan ppeiForbiddenSubFilter ketika melihat adbe.pkcs7.sha1 atau adbe.x509.rsa_sha1, dan memunculkan ppeiBadDigestAlgorithm ketika CMS itu sendiri menggunakan MD5 atau SHA-1 as the message digest (klausul 6.2.1). Itu adalah dua pemeriksaan berbeda yang menangkap kelas kelemahan yang sama pada dua lapisan berbeda

Kumpulan masalah memiliki total 26 anggota, dan masalah yang paling sering Anda temui berpusat pada struktur dan cakupan. ppeiByteRangeNotCoveringFile adalah pemeriksaan cakupan yang dijelaskan sebelumnya. ppeiForbiddenCertKey aktif ketika kamus tanda tangan membawa entri /Cert, yang dilarang oleh PAdES karena rantai sertifikat harus berada di dalam CMS SignedData.certificates. ppeiMissingSigningCertificate, ppeiMissingContentType, dan ppeiMissingMessageDigest menandai tidak adanya atribut bertanda tangan wajib, dan ppeiDetachedContentViolation menangkap tanda tangan yang secara keliru menyematkan konten yang ditandatangani alih-alih memisahkannya. Mencantumkan kumpulan tersebut mengubah penolakan mentah menjadi diagnosis yang dapat Anda catat

var
  R: TPadesValidationResult;
  Issue: TPadesValidationIssue;
begin
  R := Pdf.ValidatePades;
  if R.Issues <> [] then
    for Issue := Low(TPadesValidationIssue) to High(TPadesValidationIssue) do
      if Issue in R.Issues then
        Writeln('Issue: ',
          GetEnumName(TypeInfo(TPadesValidationIssue), Ord(Issue)));
end;

Apa yang tidak diperiksa oleh ValidatePades

ValidatePades memvalidasi struktur, not trust, and mistaking one for the other is the dangerous error. Hasil plB_LTA berarti dokumen berisi tanda tangan B-LTA yang diformat dengan baik dengan semua atribut, materi, dan stempel waktu yang diamanatkan di tempat yang tepat — itu tidak berarti tanda tangan tersebut valid secara kriptografis, bahwa rantai sertifikat mengarah ke root yang Anda percayai, atau bahwa tidak ada sertifikat dalam rantai yang telah dicabut. Validator dengan sengaja tidak melakukan verifikasi kriptografis: ia tidak menghitung ulang tanda tangan di atas ByteRange, tidak membangun atau mengevaluasi rantai kepercayaan, dan tidak memeriksa status pencabutan OCSP atau CRL. Pembagian tersebut disengaja dan berguna, karena pemeriksaan struktural cepat, sepenuhnya deterministik, dan tidak memerlukan kunci, jaringan, maupun kripto platform, sehingga ValidatePades berjalan secara identik di Windows, Linux, dan macOS sebagai Pascal murni di atas byte file. Sebaliknya, validasi rantai kepercayaan tidak dapat dipisahkan dari kebijakan — root mana yang Anda percayai, bagaimana Anda mengambil status pencabutan, seberapa ketat toleransi stempel waktu Anda — dan itu termasuk dalam tahap akhir yang bergantung pada penyimpanan sertifikat platform, jadi perlakukan hasil lulus ValidatePades sebagai gerbang penting bahwa tanda tangan dibentuk dengan benar, lalu serahkan tanda tangan yang sehat secara struktural ke verifikasi kriptografi nyata sebelum Anda mengandalkannya

Pemeriksaan struktural tersebut adalah tempat yang tepat untuk memulai, dan berpadu secara alami dengan pemeriksaan hanya-baca yang lebih luas dalam mengaudit risiko keamanan PDF dengan PDFium Component dan dengan pekerjaan kepatuhan format seperti memvalidasi dokumen PDF/X siap cetak. Setelah Anda dapat membaca dan mengklasifikasikan tanda tangan, langkah berikutnya adalah membuatnya: artikel kedua dalam seri ini membahas menandatangani PDF dengan PAdES B-B, dan artikel ketiga memperluasnya ke stempel waktu tepercaya dan tanda tangan jangka panjang B-LT dan B-LTA. Pemeriksaan tanda tangan hanya-baca dan pengklasifikasi ValidatePades yang ditunjukkan di sini adalah bagian dari PDFium Component untuk Delphi, C++Builder, dan Lazarus