Підпис поверх PDF не забороняє пізніших змін. Він фіксує діапазон байтів, а інкрементальне оновлення додає нові байти після нього, тож підпис лишається математично чинним, поки документ отримує новий вміст. Чи прийнятний цей вміст — питання політики, а DocMDP — це місце, де автор її формулює: жодних змін, лише заповнення форм і підписування, або ж те саме плюс анотації. Застосування політики означає класифікацію того, що справді змінилося, — це робить AnalyzeModifications. Наведіть її на попередню редакцію, а тоді прочитайте GetModificationLevel для загального вердикту та окремі аксесори знахідок заради рівня, номера об'єкта й опису кожної різниці
З цим на місці застосування DocMDP згортається в одне порівняння: чи обчислений рівень не вищий за рівень, який дозволяє політика
Чому підписаний PDF очікувано змінюється
Три законні випадки, і вони покривають більшість того, що ви побачите. Другий підписувач додає свій підпис. Отримувач заповнює поля форм, які автор лишив відкритими. І матеріал довгострокової валідації додається: відповіді OCSP та CRL записуються в сховище безпеки документа, щоб підпис залишався перевірним після зникнення респондерів. Останнє не просто дозволене — саме це добре керований архів робить із підписаними документами навмисно
Тож «файл зріс після підписання» не несе інформації. Питання завжди в тому, що саме додано, а відповідь мусить приходити з порівняння станів документа, а не зі спостереження за байтами. Механіку додавання в кінець охоплено в статті про інкрементальні оновлення
Класифікуйте за формою об'єкта, а не за шляхом, що її створив
Класифікатор дивиться на те, чим об'єкт є після зміни, а не на те, який виклик бібліотеки його створив. Це навмисно, бо аналіз запускається на файлах, зроблених іншим програмним забезпеченням, де жодного шляху викликів нема для огляду
Розпізнаються чотири форми. Словники інформації сховища безпеки документа та валідаційні словники, об'єкти потоку перехресних посилань, запис metadata каталогу та словники підписів з діапазоном байтів — це матеріал довгострокового архіву. Об'єкт, що несе і тип поля, і значення поля, — це заповнення форми. Об'єкт, чий тип — анотація або чий підтип входить до переліку в ISO 32000-2 Table 168, — це зміна анотації. Все інше — некласифіковане
Вилучення трактуються суворіше за додавання. Вилучений об'єкт уноситься до білого списку лише тоді, коли об'єкт на старій стороні сам був архівним матеріалом, що покриває нормальний випадок заміни сховища безпеки новішим. Усяке інше вилучення — некласифіковане, бо вилучення вмісту з підписаного документа не є тим, що рівень дозволу може уповноважити. Різниці на рівні документа суворіші ще: зміна кількості сторінок іде прямо в некласифіковані без огляду окремих об'єктів, бо жоден рівень DocMDP не дозволяє додавати чи вилучати сторінки
Білий список помиляється в бік відмови
Це правило проєктування, що керує кожним прикордонним рішенням. Зміна, неправильно класифікована як дозволена, — це підпис, що валідується над вмістом, який автор ніколи не уповноважував. Зміна, неправильно класифікована як некласифікована, — це документ, який отримає прапор і людський перегляд. Ці дві помилки не симетричні, тож білий список лишається вузьким, а нерозпізнані форми провалюються в некласифіковані замість вгадування
З цього випливає практичний наслідок, який варто передбачити: файли від незвичних генераторів часом повідомлятимуть некласифіковані зміни, що при огляді виявляться невинними. Правильна відповідь — дивитися в деталі знахідки та номер об'єкта, а не розширювати білий список, бо список, що росте, щоб замовчувати окремі звіти, перестає бути засобом контролю безпеки
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; геттер повертає
// його порядковий номер
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;
Загальний рівень — це максимум по всіх знахідках, і це єдина оборонна агрегація: документ із дев'яноста дев'ятьма архівними додаваннями та однією некласифікованою зміною — це некласифікована зміна
Унизу: відбитки, а не криптографічні хеші
Рушій порівняння, який відкриває CompareWith і на якому збудовано аналіз модифікацій, ідентифікує об'єкти відбитком їх нормалізованого тіла через некриптографічний 64-бітовий хеш, а не SHA-256. Це обміркований вибір. Структурному порівнянню потрібна детермінованість: те саме тіло об'єкта має завжди давати той самий відбиток у межах одного запуску. Стійкість до колізій йому не потрібна, бо зловмисник, що контролює обидві сторони порівняння, уже переміг іншими засобами, а оплата повного криптографічного хеша за кожен об'єкт у документі на мільйон об'єктів — реальна ціна без жодної вигоди
Два правила нормалізації важать більше за вибір хеша. Непрямі посилання згортаються у маркер-заповнювач замість розгортання в наведений вміст: розгортання копіювало б тіло спільного об'єкта в кожного посилача, тож одна маленька правка спільного дескриптора шрифта анулювала б відбиток кожного об'єкта, що до нього доходить, і звіт був би нечитабельним. І самі номери об'єктів виключено з відбитка, бо переписування може перенумерувати об'єкти, не змінюючи нічого семантичного
Зіставлення потім іде у два проходи: спершу вирівнювання за відбитками, а решту спарюють за номерами об'єктів, щоб розпізнати зміну, а не додавання плюс вилучення. Дешеві перевірки йдуть першими всюди: різницю кількості сторінок повідомлено до того, як почнеться якийсь обхід об'єктів
Пастка: самопорівняння не гарантоване як тотожне
Природний перший тест для рушія різниць — порівняти файл із самим собою і ствердити, що результат тотожний. Твердження тут не тримається, і причина повчальна. Публічний шлях завантаження та нижчий шлях завантаження документа налаштовують декодування неоднаково, тож той самий файл, завантажений двома маршрутами, може дати відбитки, що різняться для деяких об'єктів. Рушій не неправильний; два завантаження справді дали різні стани в пам'яті
Замість силкувати обидва шляхи разом, семантику порівняння сформульовано вузько: аналіз порівнює поточний стан документа з попередньою редакцією і повідомляє «тотожньо» лише тоді, коли дві множини відбитків збігаються точно. Це те питання, яке користувачі справді ставлять, і воно не вимагає взаємозамінності двох завантажувачів. Коли ви проєктуєте функцію порівняння, визначення того, що означає «те саме», — більша частина роботи, ніж саме обчислення
Де це використовувати
У двох місцях. У звіті валідації, поруч із перевіркою підписів, щоб рецензент бачив не лише те, чи підпис криптографічно цілий, а й те, що сталося з документом після; сторону підписів охоплено в підписуванні та перевірці PAdES. І у вхідній брамі, де документ, що прибув ззовні, звіряється з копією, яку ви надіслали, тож повернутий контракт із доданою анотацією трактується інакше, ніж контракт із відредагованою сторінкою
Одне застереження щодо охоплення. Цей аналіз говорить вам, що змінилося між двома редакціями однієї лінії документа. Він не говорить, чи видимий вміст вводить в оману, чи потік вигляду поля форми відповідає його значенню, чи текст, схований під накладкою, досі присутній у потоці вмісту. Це потребує окремої обробки, і сторону вилучення вмісту охоплено в статті про справжнє редагування. Точки входу аналізу та порівняння задокументовано на сторінці продукту losLab PDF Developer Library