PDF yang sudah ditandatangani lalu berubah setelahnya tidak otomatis dianggap rusak. ISO 32000-1 mengizinkan incremental update di atas tanda tangan, dan hanya sebagian dari update itu yang melanggar kebijakan yang ditetapkan penandatangan. HotPDF Component untuk Delphi dan C++Builder menjawab pertanyaan itu dengan AnalyzeLoadedSignatureRevisions, yang mengklasifikasikan setiap revisi pasca-tanda tangan dan menilainya terhadap DocMDP dan FieldMDP. Skenarionya familiar bagi siapa pun yang membangun software kontrak: pelanggan Anda menandatangani perjanjian pembelian, mengirimkannya, dan menerimanya kembali dengan halaman lampiran tertambahkan. Reader menampilkan bar kuning yang mengatakan tanda tangannya utuh tapi dokumen sudah berubah sejak ditandatangani, dan tidak ada seorang pun di ruangan itu yang bisa memastikan apakah itu alur countersignature yang normal atau seseorang diam-diam menyunting kontrak yang sudah ditandatangani
Apa yang dianggap perubahan sah setelah penandatanganan?
Sebuah perubahan sah ketika kategori semantiknya masuk dalam izin yang dinyatakan oleh tanda tangan sertifikasi. ISO 32000-1 §12.8.2.2 mendefinisikan transform DocMDP dengan nilai /P 1, 2 atau 3: 1 tidak mengizinkan perubahan sama sekali, 2 mengizinkan pengisian form dan penandatanganan, 3 mengizinkan pengisian form, penandatanganan dan anotasi. HotPDF mengekspos itu sebagai nilai THPDFDocMDPPermission dmpNoChanges, dmpFormFillAndSign dan dmpFormFillSignAndAnnotate, dengan dmpNone dicadangkan untuk hasil inspeksi yang tidak membawa transform DocMDP sama sekali
Kategori-kategori itu berurutan, dan urutan itulah mesin dari seluruh pemeriksaan. THPDFRevisionModificationLevel menjalankan rmlNone, rmlLongTermValidation, rmlFormFillAndSign, rmlAnnotations, rmlOther, sengaja disusun sehingga ordinal yang lebih besar tidak pernah kurang restriktif. Satu dokumen utuh direduksi menjadi level maksimum yang teramati di seluruh revisi setelah tanda tangan, dan perbandingan DocMDP menjadi satu pengujian integer tunggal. Satu nuansa penting sejak awal: pada dmpNoChanges, analisis tetap menerima rmlLongTermValidation. Menambahkan materi validasi DSS dan VRI atau document timestamp ke file yang sudah disertifikasi adalah pemeliharaan tanda tangan, bukan modifikasi dokumen, dan memperlakukannya sebagai pelanggaran akan merusak setiap alur kerja arsip jangka panjang yang ada
Bagaimana HotPDF membangun ulang rantai revisi?
Secara struktural, bukan heuristik. Menurut ISO 32000-1 §7.5.6, incremental update menambahkan bagian cross-reference baru yang /Prev-nya menunjuk ke bagian sebelumnya, jadi HotPDF membaca startxref dari ekor file, mem-parsing bagian di sana, mengikuti /Prev mundur dan mengulanginya, mengembalikan bagian-bagian itu dari yang tertua lebih dulu. Ada dua batas keamanan dalam loop itu dan keduanya layak diketahui saat men-triage file yang gagal: /Prev yang menunjuk ke offset yang sudah pernah dikunjungi menghentikan penelusuran dengan diagnosis siklus eksplisit alih-alih berputar terus, dan rantai yang lebih panjang dari seribu revisi ditolak sepenuhnya. Keduanya muncul di Analysis.Issue dengan fungsi mengembalikan False, dan tidak satu pun boleh diabaikan begitu saja, karena /Prev siklik adalah file yang cacat atau berbahaya, bukan sekadar tidak biasa
Empat bentuk historis muncul di dokumen nyata dan keempatnya ditangani: tabel xref tradisional yang di-parse baris demi baris, cross-reference stream yang didekompresi dan di-decode lewat field /W dan /Index-nya, file hybrid-reference yang trailer tradisionalnya membawa key /XRefStm yang di-parse dan digabungkan ke revisi yang sama (kasus producer Office, dibahas di artikel tentang hybrid cross-reference stream), dan objek yang berada di dalam kontainer ObjStm, yang penting karena update modern biasanya menaruh dictionary yang berubah dalam stream terkompresi alih-alih menulisnya langsung, sebagaimana dijelaskan di artikel tentang object stream dan incremental update. Tanda tangan menjadi titik pemisah: /ByteRange[2] + /ByteRange[3] menjadi SignedRevisionLength, dan setiap bagian pada atau melampaui offset itu adalah pasca-tanda tangan. Apakah byte range itu masih menghasilkan hash yang benar adalah pertanyaan terpisah, yang dijawab oleh VerifyLoadedSignature dan dibahas di artikel tentang verifikasi tanda tangan digital PDF
Bagaimana setiap objek yang berubah diklasifikasikan
Klasifikasi berjalan per objek, lalu merambat sepanjang referensi. Untuk setiap nomor objek yang disentuh bagian pasca-tanda tangan, HotPDF membaca body baru dan body sebagaimana adanya di snapshot yang sudah ditandatangani; body yang identik adalah rmlNone, karena producer memang menulis ulang objek tanpa mengubahnya. Pengenalnya sengaja dibuat sempit. Objek /Type /DocTimeStamp, atau yang /SubFilter-nya ETSI.RFC3161, adalah rmlLongTermValidation, begitu pula apa pun yang bisa dijangkau dari tree /DSS catalog; dictionary /Type /Sig adalah rmlFormFillAndSign. Untuk kontainer, pengujiannya adalah key mana yang berpindah, bukan apa objeknya: catalog hanya boleh menambah atau mengubah /DSS, /Extensions atau /AcroForm; dictionary AcroForm hanya /Fields, /SigFlags, /NeedAppearances, /DR, /DA atau /Q; halaman hanya /Annots; field atau widget hanya /V, /AP, /AS atau /M. Apa pun di luar himpunan itu jatuh ke rmlOther, yang persis bagaimana halaman lampiran yang ditambahkan tertangkap: menambahkan halaman menyusun ulang page tree dengan cara yang tidak dicakup daftar putih mana pun, dan tidak ada pengisian form yang sah menyerupainya
Lalu level-level itu merambat, dengan setiap kontainer mewarisi level maksimum dari anak yang berubah yang ditunjuknya, diiterasi hingga penetapannya stabil. Inilah yang membuat appearance stream berfungsi. Field teks yang diisi menulis ulang /V dan menunjuk ke stream /AP baru, dan stream itu sendiri adalah blob operator konten anonim tanpa tipe yang bisa dikenali; karena field yang memilikinya adalah rmlFormFillAndSign, stream itu mewarisi level yang sama alih-alih jatuh ke rmlOther. Perambatan yang sama membawa konteks DSS ke stream sertifikat dan revocation yang seharusnya tidak bisa diklasifikasikan
Kenapa objek yang tidak bisa dibaca dihitung sebagai pelanggaran?
Karena alternatifnya adalah validator yang dikalahkan dengan menulis sesuatu yang tidak dipahaminya. Tiga situasi berakhir di rmlOther tanpa banding di HotPDF: objek yang body-nya tidak bisa dibaca dari revisi, objek yang ditandai bebas (freed) oleh revisi, dan objek yang tidak cocok dengan pengenal mana pun di atas. Masing-masing mencatat diagnosis spesifik di field Issue revisi, sehingga operator bisa melihat nomor objek mana yang menghasilkan vonis tersebut
Membebaskan (freeing) adalah yang paling tajam dari ketiganya. Revisi pasca-tanda tangan yang menandai objek yang sebelumnya sudah didefinisikan sebagai bebas berarti telah menghapus konten dari dokumen yang sudah ditandatangani, dan tidak ada level izin di bawah §12.8.2.2 yang mengizinkannya; nomor objeknya mendarat di FreedObjectNumbers dan revisi tersebut dinaikkan ke rmlOther. Objek yang tidak bisa dibaca mengikuti logika yang sama karena alasan berbeda. Validator yang tidak bisa mem-parsing sebuah objek tidak punya dasar untuk menyebutnya tidak berbahaya, dan respons jujur atas itu bukan diam. Melaporkan konstruk yang tidak biasa tapi tidak berbahaya sebagai pelanggaran hanya berbiaya satu review manusia; kesalahan sebaliknya mengirimkan kontrak yang sudah ditandatangani dengan suntingan yang tidak disadari di dalamnya
Membaca vonis di Delphi
Pemanggilannya singkat. Muat dokumen, pilih indeks tanda tangan, baca recordnya; overload tanpa parameter membuka kembali file tempat dokumen dimuat, dan overload TStream menerima byte yang disediakan pemanggil dan memulihkan posisi stream sebelum kembali. PolicyCompliant adalah boolean tunggal yang paling dicari kebanyakan pemanggil, menggabungkan tiga keputusan independen: validitas struktural dari dictionary izin, DocMDPCompliant, dan FieldMDPCompliant. Jaga komponen-komponen itu tetap terlihat di UI Anda alih-alih menyatukannya, dan perhatikan bahwa dokumen tanpa transform DocMDP membuat DocMDPCompliant tetap True, karena tanda tangan approval biasa tidak menyatakan kebijakan apa pun untuk dilanggar dan ModificationLevel agregat kemudian bersifat deskriptif, bukan vonis
var
Pdf: THotPDF;
Analysis: THPDFSignatureRevisionAnalysis;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('contract-countersigned.pdf') > 0 then
begin
if Pdf.AnalyzeLoadedSignatureRevisions(0, Analysis) then
begin
if Analysis.PolicyCompliant then
Writeln('Post-signature changes stay inside the signing policy')
else
Writeln('Policy violation: ', string(Analysis.Issue));
end
else
Writeln('Analysis could not run: ', string(Analysis.Issue));
end;
finally
Pdf.Free;
end;
end;
Untuk triage Anda biasanya menginginkan rincian per-revisi alih-alih ringkasan, karena itu memberi tahu kapan dalam sejarah dokumen sesuatu mulai salah. Setiap entri di Analysis.Revisions membawa indeksnya dalam rantai, offset cross-reference tempatnya ditulis, level modifikasinya sendiri, dan nomor objek yang terlibat
const
LevelNames: array[THPDFRevisionModificationLevel] of string =
('none', 'long-term validation', 'form fill and sign',
'annotations', 'other');
var
I: Integer;
begin
Writeln(Format('%d revisions in chain, signature sits at index %d',
[Analysis.TotalRevisionCount, Analysis.SignedRevisionIndex]));
for I := 0 to High(Analysis.Revisions) do
Writeln(Format(' rev %d at offset %d: %s (%d changed, %d freed) %s',
[Analysis.Revisions[I].RevisionIndex,
Analysis.Revisions[I].XRefOffset,
LevelNames[Analysis.Revisions[I].ModificationLevel],
Length(Analysis.Revisions[I].ChangedObjectNumbers),
Length(Analysis.Revisions[I].FreedObjectNumbers),
string(Analysis.Revisions[I].Issue)]));
end;
FieldMDP dinilai terpisah, dan itu disengaja
Sebuah dokumen bisa memenuhi DocMDP tapi tetap tidak sah, itulah kenapa FieldMDPCompliant adalah boolean terpisah alih-alih dilipat ke dalam perbandingan level. ISO 32000-1 §12.8.2.4 mendefinisikan transform FieldMDP, dan §12.7.5.5 entri /SigFieldLock terkait, untuk membekukan field form bernama pada saat penandatanganan bahkan ketika dokumen secara keseluruhan masih mengizinkan pengisian form. Mengisi field adalah tindakan level 2; mengisi field yang dikunci penandatangan adalah pelanggaran terlepas dari levelnya. HotPDF membaca cakupannya ke THPDFFieldLockAction sebagai flaAll, flaInclude atau flaExclude, dengan flaNone untuk hasil tanpa kebijakan kunci, dan nama-namanya ke Permissions.FieldNames: flaAll mengunci semuanya, flaInclude mengunci nama yang terdaftar, flaExclude mengunci semuanya kecuali itu. Satu detail penting saat membaca hasilnya, hanya field yang sudah ada dalam snapshot yang ditandatangani yang dilaporkan di ChangedFieldNames, karena field yang dibuat sepenuhnya setelah penandatanganan tidak punya state yang ditandatangani untuk dibandingkan dan malah tertangkap oleh jalur DocMDP
var
Source: TFileStream;
Analysis: THPDFSignatureRevisionAnalysis;
I: Integer;
begin
Source := TFileStream.Create('contract.pdf', fmOpenRead or fmShareDenyWrite);
try
if Pdf.AnalyzeLoadedSignatureRevisions(0, Source, Analysis) then
if Analysis.Permissions.HasFieldMDP and (not Analysis.FieldMDPCompliant) then
for I := 0 to High(Analysis.ChangedFieldNames) do
Writeln('modified after locking: ',
string(Analysis.ChangedFieldNames[I]));
finally
Source.Free; // stream position was restored before the call returned
end;
end;
Apa yang tidak diberitahukan analisis ini
Ini tidak memverifikasi tanda tangan. AnalyzeLoadedSignatureRevisions menalar tentang struktur dan izin; apakah byte range yang ditandatangani masih menghasilkan hash yang cocok dengan nilai di blob CMS, dan apakah sertifikat penandatangan berantai ke sesuatu yang Anda percayai, dijawab oleh VerifyLoadedSignature dan VerifyLoadedSignatureWithTrust. Sebuah file bisa sepenuhnya patuh kebijakan tapi tidak berharga secara kriptografis, jadi kedua pemeriksaan itu harus berdampingan di gerbang penerimaan mana pun yang sungguhan. Ini juga tidak membaca maksud di dalam content stream: halaman yang content stream-nya diganti seluruhnya tertangkap sebagai perubahan di luar daftar putih, tapi analisisnya tidak akan memberi tahu Anda bahwa penggantian itu menukar angka pembayaran. Vonis rmlOther berarti seorang manusia harus memeriksa, bukan berarti terjadi kecurangan, dan vonis compliant berarti perubahannya cocok dengan kategori yang diizinkan, bukan berarti perubahan itu memang diinginkan. Kalau yang Anda butuhkan hanyalah apa yang dinyatakan penandatangan, tanpa penelusuran revisi, GetLoadedSignaturePermissions mengembalikan dictionary kebijakannya saja
Semua yang dijelaskan di sini berjalan native di Delphi dan C++Builder tanpa layanan penandatanganan eksternal dalam alurnya, yang membuatnya praktis untuk dijalankan pada setiap dokumen masuk alih-alih hanya yang sudah dicurigai seseorang. API tanda tangan dan revisi lengkap, termasuk metode izin dan verifikasi yang berpasangan dengannya, adalah bagian dari HotPDF Component untuk Delphi dan C++Builder