Технічна стаття

Класифікація змін у PDF після підписування

Підпис поверх PDF не забороняє пізніших змін. Він фіксує діапазон байтів, а інкрементальне оновлення додає нові байти після нього, тож підпис лишається математично чинним, поки документ отримує новий вміст. Чи прийнятний цей вміст — питання політики, а DocMDP — це місце, де автор її формулює: жодних змін, лише заповнення форм і підписування, або ж те саме плюс анотації. Застосування політики означає класифікацію того, що справді змінилося, — це робить AnalyzeModifications. Наведіть її на попередню редакцію, а тоді прочитайте GetModificationLevel для загального вердикту та окремі аксесори знахідок заради рівня, номера об'єкта й опису кожної різниці

З цим на місці застосування DocMDP згортається в одне порівняння: чи обчислений рівень не вищий за рівень, який дозволяє політика

Схема драбини рівнів модифікацій PDFlibPas від mlNone до mlUnclassified, де застосування політики DocMDP — одне порівняння в Delphi
Драбина TPLModificationLevel біжить від mlNone до mlUnclassified, а застосування DocMDP зводиться до порівняння обчисленого рівня з політикою

Чому підписаний PDF очікувано змінюється

Три законні випадки, і вони покривають більшість того, що ви побачите. Другий підписувач додає свій підпис. Отримувач заповнює поля форм, які автор лишив відкритими. І матеріал довгострокової валідації додається: відповіді OCSP та CRL записуються в сховище безпеки документа, щоб підпис залишався перевірним після зникнення респондерів. Останнє не просто дозволене — саме це добре керований архів робить із підписаними документами навмисно

Тож «файл зріс після підписання» не несе інформації. Питання завжди в тому, що саме додано, а відповідь мусить приходити з порівняння станів документа, а не зі спостереження за байтами. Механіку додавання в кінець охоплено в статті про інкрементальні оновлення

Класифікуйте за формою об'єкта, а не за шляхом, що її створив

Класифікатор дивиться на те, чим об'єкт є після зміни, а не на те, який виклик бібліотеки його створив. Це навмисно, бо аналіз запускається на файлах, зроблених іншим програмним забезпеченням, де жодного шляху викликів нема для огляду

Розпізнаються чотири форми. Словники інформації сховища безпеки документа та валідаційні словники, об'єкти потоку перехресних посилань, запис metadata каталогу та словники підписів з діапазоном байтів — це матеріал довгострокового архіву. Об'єкт, що несе і тип поля, і значення поля, — це заповнення форми. Об'єкт, чий тип — анотація або чий підтип входить до переліку в ISO 32000-2 Table 168, — це зміна анотації. Все інше — некласифіковане

Дерево рішень, яке PDFlibPas застосовує до кожного зміненого об'єкта PDF, сортуючи оновлення на архівні, заповнення форм, анотації та некласифіковані рівні
Кожен змінений об'єкт класифікується за тим, чим він є, — сховище безпеки, потік xref, поле, анотація, — ніколи не за викликом, що його створив

Вилучення трактуються суворіше за додавання. Вилучений об'єкт уноситься до білого списку лише тоді, коли об'єкт на старій стороні сам був архівним матеріалом, що покриває нормальний випадок заміни сховища безпеки новішим. Усяке інше вилучення — некласифіковане, бо вилучення вмісту з підписаного документа не є тим, що рівень дозволу може уповноважити. Різниці на рівні документа суворіші ще: зміна кількості сторінок іде прямо в некласифіковані без огляду окремих об'єктів, бо жоден рівень 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. Це обміркований вибір. Структурному порівнянню потрібна детермінованість: те саме тіло об'єкта має завжди давати той самий відбиток у межах одного запуску. Стійкість до колізій йому не потрібна, бо зловмисник, що контролює обидві сторони порівняння, уже переміг іншими засобами, а оплата повного криптографічного хеша за кожен об'єкт у документі на мільйон об'єктів — реальна ціна без жодної вигоди

Два правила нормалізації важать більше за вибір хеша. Непрямі посилання згортаються у маркер-заповнювач замість розгортання в наведений вміст: розгортання копіювало б тіло спільного об'єкта в кожного посилача, тож одна маленька правка спільного дескриптора шрифта анулювала б відбиток кожного об'єкта, що до нього доходить, і звіт був би нечитабельним. І самі номери об'єктів виключено з відбитка, бо переписування може перенумерувати об'єкти, не змінюючи нічого семантичного

Зіставлення потім іде у два проходи: спершу вирівнювання за відбитками, а решту спарюють за номерами об'єктів, щоб розпізнати зміну, а не додавання плюс вилучення. Дешеві перевірки йдуть першими всюди: різницю кількості сторінок повідомлено до того, як почнеться якийсь обхід об'єктів

Двопрохідна різниця редакцій PDF у PDFlibPas: спершу перевірка кількості сторінок, 64-бітові відбитки, вирівнювання за відбитками, тоді спарювання за номером
Рушій порівняння знімає відбитки нормалізованих тіл об'єктів, спершу повідомляє різниці кількості сторінок, потім зіставляє за відбитком та номером об'єкта

Пастка: самопорівняння не гарантоване як тотожне

Природний перший тест для рушія різниць — порівняти файл із самим собою і ствердити, що результат тотожний. Твердження тут не тримається, і причина повчальна. Публічний шлях завантаження та нижчий шлях завантаження документа налаштовують декодування неоднаково, тож той самий файл, завантажений двома маршрутами, може дати відбитки, що різняться для деяких об'єктів. Рушій не неправильний; два завантаження справді дали різні стани в пам'яті

Замість силкувати обидва шляхи разом, семантику порівняння сформульовано вузько: аналіз порівнює поточний стан документа з попередньою редакцією і повідомляє «тотожньо» лише тоді, коли дві множини відбитків збігаються точно. Це те питання, яке користувачі справді ставлять, і воно не вимагає взаємозамінності двох завантажувачів. Коли ви проєктуєте функцію порівняння, визначення того, що означає «те саме», — більша частина роботи, ніж саме обчислення

Де це використовувати

У двох місцях. У звіті валідації, поруч із перевіркою підписів, щоб рецензент бачив не лише те, чи підпис криптографічно цілий, а й те, що сталося з документом після; сторону підписів охоплено в підписуванні та перевірці PAdES. І у вхідній брамі, де документ, що прибув ззовні, звіряється з копією, яку ви надіслали, тож повернутий контракт із доданою анотацією трактується інакше, ніж контракт із відредагованою сторінкою

Одне застереження щодо охоплення. Цей аналіз говорить вам, що змінилося між двома редакціями однієї лінії документа. Він не говорить, чи видимий вміст вводить в оману, чи потік вигляду поля форми відповідає його значенню, чи текст, схований під накладкою, досі присутній у потоці вмісту. Це потребує окремої обробки, і сторону вилучення вмісту охоплено в статті про справжнє редагування. Точки входу аналізу та порівняння задокументовано на сторінці продукту losLab PDF Developer Library