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
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
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
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