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

Довгострокові підписи PDF у Delphi: PAdES B-LT та B-LTA

Щоб підписаний PDF залишався придатним для перевірки через роки (навіть після закінчення терміну дії сертифіката та зміни ключів центру сертифікації), компонент PDFium може створювати довгострокові підписи PAdES на Windows: мітку часу RFC 3161 для довіреного часу, Document Security Store (DSS) для вбудовування даних валідації та архівну мітку часу DocTimeStamp для всього файлу. Ці три елементи формують базові рівні PAdES B-T, B-LT та B-LTA, і компонент PDFium дозволяє додати їх усі за один виклик підписання. Ця функція доступна лише на Windows і контролюється властивістю PadesCryptoAvailable.

Ця стаття є третьою в серії. Перша стаття про підписання PDF у форматі PAdES B-B за допомогою PDFium VCL описує базовий підпис. Друга стаття про перевірку цифрових підписів PDF та рівнів PAdES присвячена зчитуванню підписів та визначенню їхніх рівнів. Тут ми зосередимося на архівуванні: підписах, які мають залишатися придатними для перевірки протягом років, а не тижнів, незважаючи на закінчення терміну дії сертифікатів.

Чому дійсний підпис PDF з часом стає недійсним?

Базовий підпис відповідає лише на одне питання: чи змінювалися байти файлу після підписання. Він не доводить, коли саме відбулося підписання, і ця прогалина призводить до проблем у майбутньому. Структура CMS містить атрибут часу підписання, але підписувач записує туди значення зі свого локального годинника, тому валідатор не може йому довіряти. Коли термін дії сертифіката закінчується або центр сертифікації відкликає його, суворий валідатор більше не може відрізнити підпис, зроблений під час дії сертифіката, від підпису, підробленого після закінчення цього терміну. Таким чином, підпис, який був коректним у день створення, стає неможливим для перевірки не з вашої вини.

Довгострокове архівування вирішує цю проблему двома кроками. По-перше, довірена третя сторона — служба міток часу (TSA) — криптографічно підтверджує час підписання, що виключає залежність від годинника підписувача. По-друге, всі сертифікати, відповіді валідації та списки відкликання (CRL), необхідні для побудови та перевірки ланцюжка довіри, вбудовуються всередину самого PDF-файлу. Завдяки цьому перевірка не залежить від доступності серверів у майбутньому. Стандарт PAdES (ETSI EN 319 142-1) визначає їх як базові рівні, а компонент PDFium записує всі необхідні для кожного рівня структури.

Що таке підпис PDF з підтримкою LTV або архівної мітки часу?

PAdES визначає чотири базові рівні, які доповнюють один обидва, а компонент PDFium представляє їх через перелік TPadesLevel (plB_B, plB_T, plB_LT, plB_LTA). Рівень B-B — це звичайний підпис. Рівень B-T додає довірену мітку часу поверх значення підпису. B-LT (long-term) вбудовує дані для валідації (саме це зазвичай мають на увазі під «LTV»). B-LTA (long-term з архівною міткою) обгортає весь файл ще однією міткою часу, підтверджуючи цілісність самих вбудованих даних валідації з моменту архівування.

На практиці це можна представити як сходи. Рівень B-T доводить коли. Рівень B-LT доводить за допомогою чого підпис все ще можна перевірити. Рівень B-LTA підтверджує, що весь пакет не змінювався з моменту його запечатування, і саме цей рівень можна оновлювати шляхом додавання нової мітки DocTimeStamp до закінчення терміну дії попередньої для продовження архіву на необмежений термін. Для договорів або звітів із тривалим терміном зберігання ціллю є рівень B-LTA, а B-LT — практичним мінімумом.

Додавання довіреного часу: PAdES B-T з RFC 3161

Компонент PDFium перетворює підпис B-B на рівень B-T шляхом налаштування двох полів у структурі TPadesSignOptions: встановіть Level у значення plB_T та вкажіть адресу TsaUrl. Якщо обидва поля задані, процес підписання хешує байти підпису за алгоритмом SHA-256, пакує хеш як вираз messageImprint стандарту RFC 3161, відправляє POST-запит до служби міток часу (TSA) через WinHttp та вбудовує отриманий токен як непідписаний атрибут signature-time-stamp (OID 1.2.840.113549.1.9.16.2.14) в поле unsignedAttrs структури SignerInfo, відповідно до EN 319 142-1 §6.3. Мітка часу покриває саме значення підпису, пов'язуючи довірений час із конкретним підписом.

uses
  PDFium, FPdfPades;

procedure SignWithTimestamp;
var
  Pdf: TPdf;
  Options: TPadesSignOptions;
begin
  Pdf := TPdf.Create(nil);
  try
    if not Pdf.PadesCryptoAvailable then
      raise Exception.Create('PAdES signing backend is Windows-only');
    Pdf.FileName := 'contract.pdf';
    Pdf.Active := True;

    Options := TPadesSignOptions.Default;
    Options.CertificateThumbprint := 'A1B2C3D4E5F6071829304152637485960A1B2C3D';
    Options.Level  := plB_T;
    Options.TsaUrl := 'http://timestamp.example-tsa.com/tsr';
    Options.Reason := 'Contract execution';
    // Nonce left at 0: the component derives a unique anti-replay value.

    Pdf.SignPades('contract-bt.pdf', Options);
  finally
    Pdf.Free;
  end;
end;

Властивість CertificateThumbprint — це шістнадцятковий рядок хешу SHA-1 сертифіката зі сховища «MY» поточного користувача, закритий ключ якого ви можете використовувати. Поле Nonce — це параметр захисту від повторів стандарту RFC 3161. Якщо залишити його рівним нулю, компонент згенерує унікальне значення на основі хешу SHA-256 від комбінації лічильника, системного часу, кількості тактів та ідентифікатора процесу, тому два запити в одну мілісекунду не будуть конфліктувати. Вказуйте власне значення лише за наявності криптографічного генератора випадкових чисел. Важливе застереження: рівень B-T вимагає зв'язку зі службою міток часу (TSA), тому підписання виконуватиме мережевий запит і залежатиме від доступності та затримок сервера TSA.

Вбудовування даних валідації: B-LT та B-LTA за один виклик

Перехід до довгострокової валідації потребує лише однієї зміни: встановіть Level у значення plB_LTA та вкажіть TsaUrl. Компонент PDFium виконає всі необхідні кроки в межах одного виклику SignPades. Він створює підпис B-B, додає мітку часу для B-T, вбудовує об'єкт Document Security Store для B-LT та додає архівну мітку часу для B-LTA, виконуючи кожну дію у вигляді окремого інкрементного оновлення, що зберігає попередні байти незмінними.

procedure SignForArchive;
var
  Pdf: TPdf;
  Options: TPadesSignOptions;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'contract.pdf';
    Pdf.Active := True;

    Options := TPadesSignOptions.Default;
    Options.CertificateThumbprint := 'A1B2C3D4E5F6071829304152637485960A1B2C3D';
    Options.Level    := plB_LTA;   // drives B-B -> B-T -> B-LT -> B-LTA
    Options.TsaUrl   := 'http://timestamp.example-tsa.com/tsr';
    Options.Location := 'Head Office';

    // One call writes the timestamp, the DSS and the archive DocTimeStamp.
    Pdf.SignPades('contract-lta.pdf', Options);
  finally
    Pdf.Free;
  end;
end;

Для етапу B-LT компонент PDFium будує ланцюжок сертифікатів за допомогою функції CertGetCertificateChain та записує словник /DSS, що містить сертифікат підписувача та сертифікати всіх проміжних центрів у масиві /Certs (а також відповідні масиви /OCSPs та /CRLs) разом із маркером /Extensions /ESIC Level 1, відповідно до вимог EN 319 142-1 §5.4.2. Самопідписаний кореневий сертифікат навмисно опускається, оскільки стандарт RFC 5652 §10.2.3 дозволяє це, а сам кореневий сертифікат уже має бути присутній у сховищі довіри валідатора. Результатом є підпис, який переглядач може перевірити, маючи лише сам файл документа.

Для рівня B-LTA компонент додає мітку часу документа (Document Time-stamp): словник підпису із типом /SubFilter /ETSI.RFC3161, діапазон /ByteRange якого охоплює весь файл, а поле /Contents зберігає токен RFC 3161 для цього діапазону. При цьому маркер ESIC підвищується до Level 2. Це архівний крок згідно з §5.4.3 та TS 119 142-3. Оскільки мітка DocTimeStamp охоплює як сам підпис, так і дані DSS, вона доводить наявність та незмінність вбудованих даних валідації на момент архівування, що є критично важливим для довгострокових архівів.

Оновлення вже існуючого підпису

Іноді документ уже містить підпис, і вам потрібно лише додати або оновити дані валідації (наприклад, для підвищення рівня стороннього підпису B-B до B-LT для архіву). Компонент PDFium надає прямий доступ до генератора DSS через метод SaveAsPadesDss, який додає структури /DSS та /Extensions як інкрементне оновлення, не змінюючи байти існуючого підпису.

procedure AddValidationStore;
var
  Pdf: TPdf;
  DssOpts: TPadesDssOptions;
  Cert: TPadesDssMaterial;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'already-signed.pdf';
    Pdf.Active := True;

    DssOpts := TPadesDssOptions.Default;
    DssOpts.EsicExtensionLevel := 1;         // 1 for B-LT, 2 for B-LTA
    Cert.DerBytes := LoadSignerCertDer;       // your DER-encoded certificate
    SetLength(DssOpts.Certs, 1);
    DssOpts.Certs[0] := Cert;

    Pdf.SaveAsPadesDss('already-signed-lt.pdf', DssOpts);
  finally
    Pdf.Free;
  end;
end;

Ви передаєте сертифікати (у кодуванні DER), відповіді OCSP та списки CRL, необхідні для перевірки. Встановіть властивість EsicExtensionLevel у значення 1 для сховища B-LT або 2, якщо далі додаватиметься мітка DocTimeStamp. Залиште IncludeVri у значенні False за замовчуванням: стандарт EN 319 142-1 §5.4.2.3 розглядає словник /VRI для кожного підпису як необов'язковий і рекомендує не використовувати його без особливої потреби.

Вимоги та обмеження, які варто враховувати

Ці рівні мають три важливі обмеження. По-перше, криптографічний бекенд працює лише на Windows: підписання та мітки часу використовують системні функції CNG та WinHttp, тому на інших платформах властивість PadesCryptoAvailable повертає False, а виклик SignPades генерує виняток. Перевірка структури підпису, описана в попередній статті, залишається кросплатформовою; лише запис підписів обмежений системою Windows. По-друге, рівні B-T та B-LTA залежать від служби міток часу (TSA), що передбачає мережеві запити під час підписання, а працездатність та обмеження швидкості зовнішнього сервера стають частиною вашого процесу.

По-третє, кожен рівень збільшує розмір файлу. Оскільки кожен етап записується як інкрементне оновлення без переписування файлу, DSS містить повний ланцюжок сертифікатів, а мітка DocTimeStamp резервує місце для токена, файл рівня B-LTA буде значно більшим за оригінал B-B. Це компроміс: ви витрачаєте байти зараз, щоб забезпечити можливість перевірки в майбутньому. Для більшості архівів договорів це виправданий крок, але варто протестувати це на власних документах. Перевірити досягнутий рівень підпису можна методами, описаними у статті про перевірку цифрових підписів та рівнів PAdES, а загальний аналіз безпеки документів описано у статті про аудит безпеки PDF.

Описані функції підписання PAdES, додавання DSS та архівних міток часу є частиною компонента PDFium Component для Delphi та C++Builder. На сторінці продукту можна знайти повний опис функцій підписання та системних вимог.