Artikel Teknis

Verifikasi Tanda Tangan Digital PDF di Delphi dengan HotPDF

HotPDF memverifikasi tanda tangan digital di dalam dokumen PDF yang dimuat melalui tiga metode THotPDF: GetLoadedSignatureInfo, VerifyLoadedSignature, dan VerifyLoadedSignatureEx, yang diperkenalkan pada v2.259.0. Component ini menghitung ulang hash segmen /ByteRange dari file aslinya, memeriksa atribut CMS messageDigest, dan menjalankan verifikasi RSA PKCS#1 v1.5 terhadap sertifikat penandatangan yang tertanam, lalu mengembalikan svValid ketika byte dokumen masih utuh

Skenarionya biasa saja, taruhannya tidak. Sebuah pihak lawan mengembalikan kontrak yang sudah ditandatangani, workflow Anda perlu mengarsipkannya, dan seseorang mengajukan satu-satunya pertanyaan yang penting: apakah ini dokumen yang kita kirim, byte demi byte, ditandatangani oleh sertifikat yang diklaimnya? Menjawab hal itu di dalam kode adalah sisi verifikasi dari cerita signature; sisi penandatanganannya, yakni membangun dan menanamkan signature PAdES sejak awal, dibahas di artikel pendamping tentang pembuatan tanda tangan digital PAdES dengan HotPDF. Artikel ini membahas arah sebaliknya: sebuah PDF tiba dalam keadaan sudah ditandatangani, dan Anda menginginkan putusan yang terprogram alih-alih tangkapan layar tanda centang hijau Acrobat

Bagaimana sebuah PDF yang ditandatangani membuktikan dirinya belum diutak-atik?

Sebuah signature PDF melindungi rentang byte tertentu di dalam file, bukan gagasan abstrak tentang "dokumen" itu. ISO 32000-1 §12.8 mendefinisikan mekanismenya: form field signature membawa sebuah dictionary yang entry /Contents-nya menyimpan container CMS SignedData (RFC 5652), dan array /ByteRange-nya menyebutkan region file persis yang dicakup signature tersebut, sesuai §12.8.1. Array itu adalah daftar pasangan offset dan panjang, dalam praktiknya dua segmen: semua yang berada sebelum string heksa /Contents, dan semua yang sesudahnya. Nilai signature tidak bisa mencakup dirinya sendiri, sehingga file di-hash mengelilingi lubang tersebut

Rancangan itu punya konsekuensi yang membentuk keseluruhan API-nya: verifikasi harus meng-hash byte terserialisasi yang asli, persis seperti byte itu tersimpan di disk. Model objek hasil parsing tidak berguna untuk keperluan ini, karena menserialisasi ulang dokumen yang bahkan tidak berubah sekalipun akan menghasilkan byte yang berbeda. Karena itu HotPDF melakukan verifikasi terhadap file sumber tempat dokumen dimuat, atau terhadap sebuah TStream berisi byte mentah yang Anda sediakan, tidak pernah terhadap representasi in-memory-nya

Diagram HotPDF memverifikasi PDF yang ditandatangani di Delphi dengan menghitung ulang hash kedua segmen ByteRange file sumber di sekeliling lubang Contents, sementara model in-memory hasil parsing tidak pernah di-hash karena serialisasi ulang mengubah byte
Verifikasi meng-hash kedua segmen ByteRange dari byte sumber persis sebagaimana terserialisasi; model in-memory hasil parsing tidak berguna karena menserialisasi ulang dokumen yang tidak berubah pun menghasilkan byte berbeda

Membaca metadata signature sebelum memverifikasi apa pun

GetLoadedSignatureInfo mem-parsing dictionary signature beserta container CMS-nya tanpa menyentuh satu byte dokumen pun, sehingga inilah panggilan pertama yang tepat ketika Anda hanya perlu menampilkan siapa yang menandatangani dan kapan. Field signature diindeks mulai dari 0 dalam urutan form field, dan GetLoadedSignatureFieldCount memberi tahu berapa banyak yang ada. Record THPDFSignatureInfo yang dikembalikan membawa nama field, /SubFilter, common name pada sertifikat penandatangan, distinguished name untuk subject dan issuer, nomor seri, tanggal masa berlaku, waktu penandatanganan (dari signed attribute bila ada, jika tidak dari entry /M pada dictionary), nama algoritma digest, serta string /Reason, /Location, dan /ContactInfo. Anggota Status-nya tetap svNotVerified, sebuah label jujur untuk "sudah di-parsing, belum diperiksa"

var
  Pdf: THotPDF;
  Info: THPDFSignatureInfo;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('signed-contract.pdf');
    for I := 0 to Pdf.GetLoadedSignatureFieldCount - 1 do
    begin
      Info := Pdf.GetLoadedSignatureInfo(I);
      Writeln('Field:     ', Info.FieldName);
      Writeln('Signer:    ', Info.SignerName);
      Writeln('Issuer:    ', Info.IssuerDN);
      Writeln('Algorithm: ', Info.HashAlgorithm);
      Writeln('SubFilter: ', Info.SubFilter);
    end;
  finally
    Pdf.Free;
  end;
end;

Menjalankan pemeriksaan kriptografisnya

VerifyLoadedSignatureEx melakukan verifikasi lengkap untuk dokumen yang dimuat dari file dan mengembalikan record info yang sudah terisi dalam satu panggilan: ia membuka ulang file sumber, meng-hash segmen /ByteRange dengan algoritma digest milik SignerInfo, membandingkan hasilnya dengan signed attribute messageDigest (RFC 5652 §5.4), lalu memverifikasi RSA atas signature terhadap hasil enkode ulang DER SET dari signed attributes tersebut. Ketika sebuah signature tidak membawa signed attributes, pemeriksaan RSA berjalan langsung atas hash dokumen. Signature yang didukung adalah RSA PKCS#1 v1.5 dengan digest SHA-1, SHA-256, SHA-384, atau SHA-512, yang mencakup subfilter adbe.pkcs7.detached dan ETSI.CAdES.detached yang dihasilkan tool penandatanganan arus utama

Pipeline VerifyLoadedSignatureEx di HotPDF untuk Delphi: buka ulang file sumber, hash segmen ByteRange, bandingkan dengan signed attribute messageDigest CMS, lalu verifikasi RSA PKCS#1 v1.5, menghasilkan svValid, svDigestMismatch, atau svSignatureInvalid
VerifyLoadedSignatureEx menghitung ulang hash segmen ByteRange, membandingkannya dengan signed attribute messageDigest, dan memverifikasi RSA atas DER SET dari signed attributes sebelum melaporkan svValid atau status kegagalan yang spesifik
var
  Status: THPDFSignatureVerifyStatus;
  Info: THPDFSignatureInfo;
begin
  Status := Pdf.VerifyLoadedSignatureEx(0, Info);
  case Status of
    svValid:
      if Info.CoversWholeDocument then
        Writeln('Valid; signature covers the whole file')
      else
        Writeln('Valid; file was extended after signing');
    svDigestMismatch:
      Writeln('Document bytes changed after signing');
    svSignatureInvalid:
      Writeln('RSA check failed over signed attributes');
    svUnsupportedAlgorithm:
      Writeln('Non-RSA key or unknown digest algorithm');
    svMalformed:
      Writeln('CMS container could not be parsed');
    svSourceUnavailable:
      Writeln('No source bytes; use the TStream overload');
  end;
end;

Ada dua detail implementasi yang layak diketahui karena keduanya menjelaskan kegagalan yang tampak misterius dari luar. Pertama, pemeriksaan signed attributes itu rewel soal enkode: di dalam file, atribut-atribut tersebut ditandai [0] IMPLICIT, namun signature-nya dihitung atas bentuk DER SET OF mereka, sehingga verifier menandai ulang sebelum meng-hash, persis seperti yang dituntut RFC 5652 §5.4. Sebuah verifier buatan sendiri yang meng-hash byte apa adanya sebagaimana muncul di file akan menolak setiap dokumen yang ditandatangani dengan benar. Kedua, /Contents secara konvensional diisi angka nol hingga memenuhi anggaran byte yang dicadangkan, sehingga verifier memangkas blob DER itu sampai panjang sebenarnya dari SEQUENCE terluarnya sebelum mem-parsing; deretan nol di ekor yang terlihat seperti sampah adalah hal normal, bukan korupsi. Keluarga bahaya parsing ASN.1 yang sama, pada sisi impor sertifikat, menjadi topik artikel tentang pengerasan keamanan PKCS#12 dan ASN.1 di HotPDF

Apa sebenarnya yang dijamin oleh signature yang valid?

svValid berarti persis ini: byte yang disebutkan oleh /ByteRange menghasilkan hash yang sama dengan nilai yang ditandatangani penandatangan, dan signature-nya terverifikasi di bawah public key dari sertifikat yang tertanam di dalam container CMS. Itu adalah integritas byte ditambah keterikatan key, tidak lebih. Validasi rantai sertifikat dan trust secara eksplisit berada di luar cakupan verifier HotPDF: ia tidak menelusuri rantai sampai ke root, tidak memeriksa revokasi, dan tidak berkonsultasi dengan trust store mana pun. Sertifikat self-signed dari penyerang yang menandatangani ulang dokumen yang telah diubah tetap akan terverifikasi sebagai svValid, karena matematikanya konsisten secara internal. Apakah penandatangan itu benar-benar seperti yang diakuinya, dan apakah ada yang pantas memercayainya, adalah keputusan kebijakan yang menjadi milik lapisan terpisah, entah itu whitelist sertifikat organisasi Anda, certificate store Windows, atau sebuah validation authority

Flag CoversWholeDocument menjaga celah yang lebih halus. Sebuah signature hanya pernah mencakup /ByteRange-nya, dan mekanisme incremental update pada PDF mengizinkan penambahan konten setelah sebuah signature tanpa membatalkannya, yang memang disengaja dan menjadi cara kerja workflow multi-signature. Flag itu dihitung saat verifikasi dan bernilai true hanya ketika kedua segmen ditambah celah /Contents merentang seluruh file. Ketika svValid datang dengan CoversWholeDocument bernilai false, revisi yang ditandatangani memang utuh namun file tersebut berisi tambahan yang datang belakangan, dan apa yang diubah oleh tambahan itu adalah sesuatu yang perlu diputuskan sendiri oleh workflow Anda: ditoleransi atau tidak

Diagram cakupan tentang apa yang dijamin svValid pada verifikasi signature HotPDF: integritas byte ByteRange dan keterikatan key, sementara penelusuran rantai, revokasi, dan trust store berada di luar cakupan dan CoversWholeDocument menandai incremental update yang ditambahkan
svValid berarti integritas byte ditambah keterikatan key dan tidak lebih; penelusuran rantai, revokasi, dan trust store adalah milik lapisan kebijakan terpisah, dan CoversWholeDocument=false menandakan byte yang ditambahkan setelah penandatanganan

Dokumen yang dimuat dari stream dan dokumen terenkripsi butuh byte sumbernya sendiri

VerifyLoadedSignature dan VerifyLoadedSignatureEx versi tanpa parameter bergantung pada ingatan component tentang file asal dokumen tersebut. Muat dokumen dari sebuah stream, dan tidak ada nama file yang bisa dibuka ulang; hal yang sama berlaku setelah jalur reload berpassword yang dipakai untuk dokumen terenkripsi, workflow yang diuraikan di artikel tentang enkripsi PDF AES-256 dengan HotPDF. Pada kedua kasus itu, overload yang bersandar pada file mengembalikan svSourceUnavailable alih-alih menebak-nebak. Solusinya adalah overload TStream, yang membiarkan Anda menyerahkan byte mentah aslinya dari mana pun Anda menyimpannya, sebuah file yang masih Anda punya, buffer memori, atau blob di database

var
  Src: TFileStream;
  Status: THPDFSignatureVerifyStatus;
  Info: THPDFSignatureInfo;
begin
  // Dokumen yang dimuat dari stream: component tidak menyimpan nama
  // file sumber, jadi sediakan sendiri byte aslinya.
  Src := TFileStream.Create('signed-contract.pdf',
    fmOpenRead or fmShareDenyWrite);
  try
    Status := Pdf.VerifyLoadedSignature(0, Src, Info);
    if Status <> svValid then
      Writeln('Verification failed: ', Ord(Status));
  finally
    Src.Free;
  end;
end;

Melaporkan apa yang tidak bisa Anda verifikasi

Sebuah verifier yang hanya mengenal "valid" dan "tidak valid" akan salah melaporkan dokumen yang sekadar tidak dipahaminya, sehingga enumerasi status di sini memisahkan kasus-kasus yang seharusnya dibedakan oleh UI Anda. svDigestMismatch berarti byte dokumen berubah setelah penandatanganan, sinyal klasik adanya pengutak-atikan. svSignatureInvalid berarti hash byte-nya cocok namun pemeriksaan RSA gagal, yang menunjuk ke nilai signature yang rusak atau dipalsukan. svUnsupportedAlgorithm adalah jawaban jujur untuk key ECDSA dan digest yang tidak dikenali: signature-nya mungkin saja sangat baik, HotPDF sekadar tidak bisa memeriksanya, dan melaporkannya sebagai "tidak valid" berarti mencemarkan nama dokumen yang sehat. svMalformed menandai container CMS yang sama sekali tidak bisa di-parsing. Untuk pemeriksaan bergaya gerbang, VerifyAllLoadedSignatures mengembalikan true hanya ketika setidaknya ada satu field signature dan setiap satu di antaranya terverifikasi sebagai svValid, sebuah boolean tunggal yang praktis untuk pipeline penyerapan arsip yang menolak apa pun yang kurang dari itu

Verifikasi signature, penandatanganan PAdES, enkripsi AES-256, dan API penyuntingan dokumen yang dimuat semuanya hadir dalam satu library VCL native yang sama untuk Delphi dan C++Builder, tanpa ketergantungan DLL eksternal; daftar fitur lengkap dan versi IDE yang didukung ada di halaman produk HotPDF Delphi Component