חתימה על PDF אינה אוסרת שינויים מאוחרים. היא קובעת טווח בייטים, ועדכון מצטבר מוסיף בייטים חדשים אחריו, כך שהחתימה נשארת תקפה מתמטית בזמן שהמסמך צובר תוכן חדש. האם התוכן הזה מקובל היא שאלה של מדיניות, ו-DocMDP הוא המקום שבו המחבר קובע את המדיניות: בלי שינויים בכלל, מילוי טפסים וחתימה בלבד, או אלה ועוד annotations. אכיפת המדיניות פירושה סיווג מה שהשתנה בפועל, וזה בדיוק מה ש-AnalyzeModifications עושה. מכוונים אותו אל גרסה קודמת, ואז קוראים את GetModificationLevel לפסק הדין הכולל ואת המאחזרים לכל ממצא עבור הרמה, מספר ה-object והתיאור של כל הבדל
כשזה במקום, אכיפת DocMDP מתכווצת להשוואה אחת: האם הרמה המחושבת בגבול או מתחת לרמה שהמדיניות מתירה
למה צפוי ש-PDF חתום ישתנה
שלושה מקרים לגיטימיים, והם מכסים את רוב מה שתראו. חותם שני מוסיף את החתימה שלו. נמען ממלא שדות טופס שהמחבר השאיר פתוחים. וחומר אימות לטווח ארוך מתווסף: תגובות OCSP ו-CRL שנכתבות אל מאגר האבטחה של המסמך כדי שהחתימה תישאר ניתנת לאימות אחרי שהמגיבים נעלמו. המקרה האחרון אינו רק מותר, הוא מה שארכיון מנוהל היטב עושה למסמכים חתומים בכוונה
לכן "הקובץ גדל אחרי החתימה" אינו נושא מידע. השאלה היא תמיד מה נוסף, והתשובה חייבת לבוא מהשוואת מצבי מסמך ולא מצפייה בבייטים. מכניקת ההוספה עצמה מכוסה במאמר העדכון המצטבר
מסווגים לפי צורת ה-object ולא לפי הנתיב שיצר אותו
המסווג מסתכל על מה ש-object הוא אחרי השינוי, ולא על איזו קריאת ספרייה יצרה אותו. זה מכוון, כי הניתוח רץ על קבצים שהופקו על ידי תוכנה אחרת, שבהם אין נתיב קריאה זמין לבחינה
ארבע צורות מזוהות. מילוני מידע של מאגר אבטחת מסמך ושל מידע הקשור לאימות, objects של cross-reference stream, ערך ה-metadata של הקטלוג, ומילוני חתימות שנושאים טווח בייטים הם חומר ארכיון לטווח ארוך. object שנושא גם טיפוס שדה וגם ערך שדה הוא מילוי טפסים. object שהטיפוס שלו annotation, או שה-subtype שלו הוא אחד מאלה המפורטים בטבלה 168 של ISO 32000-2, הוא שינוי annotation. כל השאר אינו מסווג
הסרות מטופלות בקפדנות גדולה יותר מהוספות. object שהוסר נכנס לרשימה הלבנה רק כשה-object בצד הישן היה בעצמו חומר ארכיון, מה שמכסה את המקרה הרגיל של מאגר אבטחה שמוחלף בחדש יותר. כל הסרה אחרת אינה מסווגת, כי מחיקת תוכן ממסמך חתום אינה דבר שרמת הרשאה מאשרת. הבדלים ברמת המסמך קפדניים עוד יותר: שינוי במספר העמודים מגיע ישירות ללא-מסווג בלי לבחון objects בודדים, כי אף רמת DocMDP אינה מתירה הוספה או הסרה של עמודים
הרשימה הלבנה טועה לכיוון הסירוב
זה כלל העיצוב שמכתיב כל החלטה גבולית. שינוי שסווג שלא בצדק כמותר הוא חתימה שמאומתת על תוכן שהמחבר מעולם לא אישר. שינוי שסווג שלא בצדק כלא-מסווג הוא מסמך שמסומן ונבחן על ידי אדם. שתי הטעויות האלה אינן סימטריות, ולכן הרשימה הלבנה נשארת צרה וצורות בלתי מזוהות נופלות אל לא-מסווג במקום לנחש
לזה השלכה מעשית ששווה לצפות: קבצים מיצרנים בלתי שגרתיים ידווחו לפעמים על שינויים לא מסווגים שהם, עם בחינה, תמימים. התגובה הנכונה היא להסתכל על פרטי הממצא ומספר ה-object ולא להרחיב את הרשימה הלבנה, כי רשימה לבנה שצומחת כדי להשתיק דיווחים בודדים מפסיקה להיות בקרת אבטחה
uses
PDFlibrary, PDFlibCompare;
var
Pdf: TPDFlib;
I, Level: Integer;
begin
Pdf := TPDFlib.Create(nil);
try
Pdf.LoadFromFile('contract-countersigned.pdf', '');
if Pdf.AnalyzeModifications('contract-as-signed.pdf', '') < 0 then
raise Exception.Create('the earlier revision could not be loaded');
// TPLModificationLevel בסדר mlNone, mlLTAUpdates, mlFormFilling,
// mlAnnotations, mlUnclassified; ה-getter מחזיר את ה-ordinal שלו
Level := Pdf.GetModificationLevel;
// אכיפת DocMDP היא עכשיו השוואה אחת מול המדיניות
if Level > Ord(mlFormFilling) then
for I := 0 to Pdf.GetModificationFindingCount - 1 do
Report.Add(Format('object %d, level %d: %s',
[Pdf.GetModificationFindingObjNum(I),
Pdf.GetModificationFindingLevel(I),
Pdf.GetModificationFindingDetail(I)]));
finally
Pdf.Free;
end;
end;
הרמה הכוללת היא המקסימום על כל הממצאים, וזה הצבירה היחיד שניתן להגנה: מסמך שמכיל תשעים ותשע הוספות ארכיון ושינוי לא מסווג אחד הוא שינוי לא מסווג
מתחת לפני השטח: טביעות אצבע, לא hash קריפטוגרפיים
מנוע ההשוואה ש-CompareWith חושף, ושעליו נבנה ניתוח השינויים, מזהה objects לפי טביעת אצבע של הגוף המנורמל שלהם באמצעות hash של 64 ביט שאינו קריפטוגרפי במקום SHA-256. זו בחירה מושכלת. מה שהשוואה מבנית צריכה הוא דטרמיניזם: גוף object זהה חייב להפיק תמיד את אותה טביעת אצבע בתוך ריצה. הוא אינו צריך עמידות להתנגשויות, כי תוקף ששולט בשני צידי ההשוואה כבר ניצח באמצעים אחרים, ותשלום על hash קריפטוגרפי מלא על כל object במסמך של מיליון objects הוא עלות אמיתית ללא תועלת
שני כללי נרמול חשובים יותר מבחירת ה-hash. הפניות עקיפות מתקפלות לאסימון placeholder במקום להיות מורחבות אל התוכן המופנה: הרחבה הייתה מעתיקה את גוף object משותף אל כל מפנה, כך שעריכה קטנה אחת ב-descriptor של גופן משותף הייתה מבטלת את טביעת האצבע של כל object שמגיע אליו, והדוח היה בלתי קריא. ומספרי objects עצמם אינם נכללים בטביעת האצבע, כי כתיבה מחדש יכולה למספר objects מחדש בלי לשנות דבר סמנטי
ההתאמה רצה אז בשני מעברים, מיישרת לפי טביעת אצבע קודם ומזווגת את השאר לפי מספר object כדי לזהות שינויים ולא הוספה בתוספת הסרה. בדיקות זולות באות קודם לכל אורך הדרך: הבדל במספר העמודים מדווח לפני שמתחיל מעבר objects כלשהו
מלכודת: השוואה-עצמית אינה מובטחת להיות זהה
הבדיקה הראשונה הטבעית למנוע diff היא להשוות קובץ מול עצמו ולטעון שהתוצאה זהה. הטענה הזאת אינה מתקיימת כאן, והסיבה למדה. נתיב הטעינה הציבורי ונתיב טעינת המסמך הנמוך יותר אינם מגדירים decoding באופן זהה, כך שאותו קובץ שנטען דרך שני הנתיבים יכול להפיק טביעות אצבע שונות עבור objects מסוימים. המנוע אינו שגוי; שתי הטעינות הפיקו באמת מצבי זיכרון שונים
במקום לכפות את שני הנתיבים יחד, סמנטיקת ההשוואה נוסחה בצמצום: הניתוח משווה את מצב המסמך הנוכחי מול גרסה קודמת, ומדווח זהה רק כששתי קבוצות טביעות האצבע מתלכדות במדויק. זו השאלה שמשתמשים באמת שואלים, והיא אינה דורשת ששני הטוענים יהיו ניתנים להחלפה. כשמעצבים תכונת השוואה, הגדרת משמעות "זהה" היא יותר מהעבודה מאשר לחשב אותה
היכן להשתמש בזה
שני מקומות. בדוח אימות, לצד בדיקת חתימות, כך שסוקר רואה לא רק אם החתימה שלמה קריפטוגרפית אלא מה קרה למסמך אחר כך; צד החתימה מכוסה בחתימה ואימות PAdES. ובשער קליטה, שבו מסמך שמגיע מבחוץ נבדק מול העותק ששלחתם, כך שחוזה שהוחזר עם annotation שנוסף מטופל אחרת מחוזה עם עמוד שנערך
הסתייגות אחת לגבי ההיקף. הניתוח הזה אומר לכם מה השתנה בין שתי גרסאות של אותו שושלת מסמך. הוא לא אומר לכם אם התוכן הנראה מטעה, אם appearance stream של שדה טופס תואם את ערכו, או אם טקסט מוסתר תחת שכבת overlay עדיין קיים ב-content stream. אלה דורשים טיפול נפרד, וצד הסרת התוכן של זה מכוסה במאמר ה-redaction האמיתי. נקודות הכניסה לניתוח ולהשוואה מתועדות בדף המוצר של losLab PDF Developer Library