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

Докази LTV та початкові значення PAdES у HotPDF

PDF, який ви щойно підписали, — це підпис B-B і нічого більше. Він доводить, хто підписав і що байти не зрушили, але не несе доказу, що сертифікат підписувача був чинний на момент підписування, тож перевіряльник за роки віднині муситиме шукати дані відкликання, яких може вже не існувати. Закриття цієї прогалини означає запис відповідей OCSP та CRL у документне Document Security Store, і в HotPDF це один виклик: PopulatePAdESLTVEvidence проходить кожен завантажений підпис, виводить запити відкликання з множини сертифікатів, виконує їх через транспорт, який ви постачаєте, і записує отриманий матеріал разом із ланцюгом CMS до DSS. Вона повертає кількість підписів, чиї докази осіли, або мінус один, коли документ узагалі не має поля підпису

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

Чому бібліотека відмовляється робити власний HTTP?

Бо місця, що вимагають підписів B-LT, — це місця, де бібліотеці не можна довіряти мережу. Служби підписування працюють за автентифікаційними проксі з корпоративними коренями. Відрізані від мережі рівні підписування не мають маршруту до респондера і мусять харчуватися кешованими доказами. Режими аудиту вимагають, щоб кожен вихідний запит журналював застосунок, а не ховався в залежності. І тестові набори потребують детермінованих відповідей, що неможливо, якщо бібліотека сама набирає номер

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

Потік PopulatePAdESLTVEvidence у HotPDF: транспорт FetchEvidence від викликача, поля запису запиту та статуси на підпис
Кожен мережевий байт проходить через ваш зворотний виклик FetchEvidence, і кожен підпис отримує власний статус, тож один таймаут ніколи не перериває прохід
function FetchEvidence(const Request: THPDFSignatureEvidenceRequest;
  Attempt: Integer; CancellationToken: THPDFCancellationToken;
  out Response: TBytes; out RetryAfterMS: Cardinal;
  out ErrorMessage: UnicodeString): THPDFSignatureEvidenceTransportStatus;
begin
  RetryAfterMS := 0;
  try
    // Request.Kind каже, чи це OCSP POST, чи CRL GET;
    // Request.ContentType і Request.Body вже підготовлені,
    // а Request.MaxResponseBytes — стеля, яку ви мусите дотримати
    Response := HttpExchange(Request.URI, Request.ContentType,
      Request.Body, Request.MaxResponseBytes);
    Result := setsSucceeded;
  except
    on E: Exception do
    begin
      ErrorMessage := E.Message;
      // setsRetry дозволяє політиці повторів відступити;
      // setsPermanentFailure використовуйте для 404 або поганої URL
      Result := setsRetry;
    end;
  end;
end;

// Оновлення B-B до B-LT одним викликом для кожного підпису
// у завантаженому файлі
var
  Pdf: THotPDF;
  Upgraded: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.BeginIncrementalUpdate('signed.pdf');
    Upgraded := Pdf.PopulatePAdESLTVEvidence(FetchEvidence,
      THPDFSignatureEvidenceRetryPolicy.Default);
    if Upgraded > 0 then
      // Збереження лише з додаванням: байти, які покривають
      // наявні підписи, зберігаються дослівно
      Pdf.SaveIncrementalUpdate('signed-lt.pdf');
  finally
    Pdf.Free;
  end;
end;

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

Ланцюг, який CMS забула вмістити

Перевірці відкликання потрібен сертифікат видавця, і дивовижна кількість стеків підписування опускає проміжні з контейнера CMS. Маршрут відновлення — розширення Authority Information Access, метод доступу 1.3.6.1.5.5.7.48.2, який оголошує URL, звідки можна завантажити сертифікат видавця. HPDFFetchAIAIntermediates проходить ті URL через той самий транспорт, розбирає DER з кожної відповіді й повертає лише ті сертифікати, яких CMS уже не мала, з ключем за хешем DER, тож дублікати та цикли не можуть закрутитися

Дві деталі вирішують, чи це працює проти справжніх центрів сертифікації. Перша — кодування: кінцеві точки CA подають сертифікат як голий DER майже так само часто, як і в PEM-броні, і немає надійного типу вмісту, щоб розрізнити їх. Стійка проба — текстова, потім структурна. Шукайте маркер -----BEGIN CERTIFICATE-----, знімайте броню та декодуйте base64, якщо вона є, а в обох шляхах пересвідчіться, що перший байт результату — $30, тег DER для SEQUENCE. Друга — глибина: отриманий проміжний сам може оголошувати AIA URL для власного видавця, тож прохід додає нові кандидатів до черги й завершує ланцюги, яким бракує двох-трьох стрибків. Це треба обмежувати, для чого і слугує параметр MaxFetch

Схема добудови ланцюга AIA для HotPDF: отримання URL caIssuers, проба PEM проти DER, дедуплікація за хешем DER і стеля глибини MaxFetch
HPDFFetchAIAIntermediates проходить URL caIssuers через той самий транспорт, пробуючи PEM-броню та обмежуючи чергу через MaxFetch

Що таке початкове значення підпису і чому воно мовчки зазнає невдачі?

Початкове значення (seed value) — це обмеження, яке автор документа чіпляє до поля підпису, щоб сказати підписувачу, який вид підпису прийнятний: який SubFilter, який алгоритм дайджесту, які причини, яка найменша версія PDF, чи мусять дані відкликання бути вбудовані. Воно живе в словнику /SV на полі і визначене в ISO 32000-1 §12.7.5.5. HotPDF записує його через AttachPAdESSeedValue і перевіряє через CheckLoadedSignatureSeedValue, що повертає True, коли поле без обмежень або кожна присутня умова проходить, а на False називає першу невдалу умову через вихідний параметр, який можна класти прямо в повідомлення помилки

Механізм, через який початкові значення легко зробити неправильно, — запис прапорців /Ff, описаний у §12.7.5.5.3. Встановлений біт позначає свою умову як обов'язкову: невідповідність — помилка, і підписувач мусить відмовити. Очищений біт позначає ту саму умову як уподобання: значення фільтрує те, що інтерфейс має пропонувати, і нічого більше. З цього випливають дві пастки. Перша: /Ff живе всередині словника /SV, а не на анотації віджета, тож код, що читає рівень поля /Ff, вічно отримує порожню відповідь і робить висновок, що нічого не примусово. Друга: призначення бітів — не проста низка один, два, чотири, вісім; у HotPDF записувач випромінює 2 для SubFilter, 4 для MinVersion, 32 для AddRevInfo і 64 для DigestMethod. Читач, що припускає послідовні біти, декодує кожну умову як необов'язкову і проходить кожен тест, окрім того одного, що важить

Таблиця бітів прапорців початкових значень для підписування PAdES у HotPDF із битами Ff 2, 4, 32 та 64 і обробкою обов'язкових проти бажаних умов
Запис /Ff живе всередині /SV, і кожна позиція біта вирішує, чи невідповідність — тверда відмова, чи уподобання інтерфейсу
var
  Violation: AnsiString;
begin
  // Запитайте в поля, чи дозволений профіль, яким збираємося підписувати
  if not Pdf.CheckLoadedSignatureSeedValue(0, 'ETSI.CAdES.detached',
       'SHA256', 'Approved for payment', 1, Violation) then
    raise Exception.Create('Signature field rejects this profile: ' +
      String(Violation));
  // Умову задоволено: продовжуємо прохід підписування
end;

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

Де це на драбині LTV

Чотири сходинки, і кожна потребує нижчу. B-B — голий підпис. B-T додає довірений часовий штамп, який фіксує час підписування, тож перевіряльник знає, на який момент оцінювати відкликання. B-LT додає докази відкликання до DSS, що і автоматизує PopulatePAdESLTVEvidence. B-LTA додає часові штампи документа, які поновлюються до того, як попередній слабне, подовжуючи чинність нескінченно; HotPDF відкриває це як RenewPAdESLTATimestamp, що додає новий штамп як інкрементальну редакцію і лишає кожен попередній підпис, штамп і запис DSS недоторканими

Модель інкрементальних оновлень — єдиний правильний спосіб додавати докази до підписаного документа, бо переписування файла зламало б діапазони байтів, які покривають наявні підписи. Якщо вам треба міркувати про те, що змінилося між редакціями, і чи ті зміни такого сорту, який підпис дозволяє, — той аналіз охоплено окремо в аналізі редакцій DocMDP і FieldMDP. Сам конвеєр підписування, включно з джерелами сертифікатів і пастками порядку байтів, — у поясненні підписування PAdES, а сторона перевірки — у перевірці підписів на завантажених документах

Одне практичне попередження про порядок. Збирайте докази якнайшвидше після підписування, в ідеалі в тому самому завданні. Респондери, які можуть відповісти за сертифікат, онлайн, поки сертифікат чинний, і зникають роками пізніше, тож документ, що лишає ваш конвеєр як B-B, може більше ніколи не бути оновлюваним. HotPDF працює як власний компонент VCL для Delphi та C++Builder, і весь прохід доказів — у процесі, окрім вашого власного транспорту; підтримувані профілі наведені на сторінці продукту HotPDF Delphi PDF component