07 июня 2021

Программное добавление цифровой подписи в PDF документ

    Одной из интересных и важных особенностей документов в формате PDF, является возможность подписи документа с помощью цифрового сертификата. Цифровая подпись, как и рукописная на бумаге, позволяет идентифицировать человека, подписавшего документ. Но, в отличие от рукописной, такую подпись сложнее подделать. Кроме того, она позволяет определить, был ли документ изменен после подписания.
    Используемая мной для работы с документами в формате PDF библиотека Foxit Quick PDF Library (бывшая Debenu Quick PDF Library) имеет два варианта реализации подписи документа с помощью цифрового сертификата.
    Первый вариант очень простой. Он создает невидимую подпись и реализуется с помощью вызова одной функции:
procedure SignFile(const sPdfFileName, sPdfPassword, sPfxFileName, sPfxPassword: String);
var
  pdf: TDebenuPDFLibrary;
  iSignResult: Integer;
begin
  pdf := TDebenuPDFLibrary.Create;
  try
    // Подписываем документ сертификатом в формате PKCS#12
    iSignResult := pdf.SignFile(sPdfFileName,             // наименование документа для подписи
                     sPdfFileName,                        // пароль документа
                     'Test signature field',              // именованное поле для подписи
                     // наименование подписанного документа
                     ChangeFileExt(sPdfFileName, '-signed.pdf'),
                     sPfxFileName,                        // файл сертификата
                     sPfxPassword,                        // пароль сертификата
                     'Тестирование подписи',              // причина подписания
                     'Belarus',                           // местонахождение
                     'https://it-blackcat.blogspot.com'); // контактная информация
    // Проверяем результат выполнения функции подписи
    WriteSignResult(iSignResult);
  finally
    pdf.Free;
  end;
end;

procedure WriteSignResult(const iSignResult: Integer);
begin
    case iSignResult of
      1 : WriteLn('The file was signed successfully');
      2 : WriteLn('Input PDF not found');
      3 : WriteLn('Input PDF cannot be read');
      4 : WriteLn('Input PDF password incorrect');
      5 : WriteLn('Certificate file not found');
      6 : WriteLn('Certificate file is invalid');
      7 : WriteLn('Incorrect certificate password');
      8 : WriteLn('Unknown certificate format');
      9 : WriteLn('No private key found in certificate file');
      10: WriteLn('Could not write output file');
      11: WriteLn('Could not apply signature');
      12: WriteLn('The signature field name was blank');
      13: WriteLn('The input file cannot be signed because the "NeedAppearances" ' +
                  'flag is set to true');
      14: WriteLn('Certificate not found in store');
      15: WriteLn('The input file cannot be signed due to an xref table issue');
      16: WriteLn('Could not apply timestamp to signature');
      else WriteLn('Unknown result code: ' + iSignResult.ToString);
    end;
end;
Исходный код приведенных процедур содержит подробные комментарии, поэтому нет смысла его описывать Добавлю только, что для подписи используется файл формата PKCS#12 (PFX-файл), который содержит закрытый ключ и сертификат X.509. Если взглянуть на свойства добавленной в документ подписи, то мы найдем там параметры, которые задали из программы и информацию о том, что этот документ после подписания не изменялся:
Свойства, подписанного цифровой подписью PDF документа
    Второй вариант немного сложнее и требует написания нескольких строк кода. Но он позволяет добавить в документ видимую цифровую подпись:
procedure SignFile(const sPdfFileName, sPdfPassword, sPfxFileName, sPfxPassword: String);
var
  pdf: TDebenuPDFLibrary;
  iSignProcessID: Integer;
begin
  pdf := TDebenuPDFLibrary.Create;
  try
    // Создаем новый процесс цифровой подписи
    iSignProcessID := pdf.NewSignProcessFromFile(sPdfFileName, sPdfPassword);
    if iSignProcessID > 0
      then try
        // Используем для подписи файл формата PKCS#12 с закрытым ключом и сертификатом X.509
        pdf.SetSignProcessPFXFromFile(iSignProcessID, sPfxFileName, sPfxPassword);
        // Задаем информацию для подписи
        pdf.SetSignProcessInfo(iSignProcessID,
                               'Тестирование подписи',              // причина подписания
                               'Belarus',                           // местонахождение
                               'https://it-blackcat.blogspot.com'); // контактная информация
        // Задаем именованное поле для подписи
        pdf.SetSignProcessField(iSignProcessID, 'Test signature field');

        // Если поле с указанным именем не найдено, то на первой странице будет создано
        // новое невидимое поле нулевой ширины и высоты, которое необходимо настроить

        // Задаем номер страницы, на которой будет размещено поле для подписи
        pdf.SetSignProcessFieldPage(iSignProcessID, 1);
        // Задаем расположение и размер поля для подписи
        pdf.SetSignProcessFieldBounds(iSignProcessID, 95, 815, 85, 20);
        // Задаем полю для подписи файл с изображением
        pdf.SetSignProcessFieldImageFromFile(iSignProcessID, 'Bill-Gates.jpg', 0);

        // Завершаем процесс подписи и записываем подписанный документ в файл
        pdf.EndSignProcessToFile(iSignProcessID, ChangeFileExt(sPdfFileName, '-signed.pdf'));
        // Проверяем результат выполнения процесса подписи
        WriteSignResult(pdf.GetSignProcessResult(iSignProcessID));
      finally
        // Удаляем процесс подписи из памяти
        pdf.ReleaseSignProcess(iSignProcessID);
      end
      else WriteLn('Input PDF not found')
  finally
    pdf.Free;
  end;
end;
Все вызываемые в этой процедуре методы являются функциями, но их результат можно игнорировать. Например, результат вызова метода EndSignProcessToFile всегда будет нулевым. Чтобы проверить результат выполнения процесса подписи необходимо воспользоваться методом GetSignProcessResult. В свойствах подписи, добавленной в документ вторым способом, мы видим, что именованное поле для подписи "Test signature field" видимое и находится на первой странице, а после надписи "Подпись:" находится изображение, загруженное методом SetSignProcessFieldImageFromFile.
Свойства, подписанного визуальной цифровой подписью PDF документа
Если кликнуть мышкой по изображению подписи можно посмотреть ее свойства (как на первом скриншоте).
    Загрузку сертификата из файла формата PKCS#12 используя метод SetSignProcessPFXFromFile можно заменить на выбор сертификата из хранилища сертификатов Windows по его SHA1-хешу:
...
// pdf.SetSignProcessPFXFromFile(iSignProcessID, sPfxFileName, sPfxPassword);
// Выбираем сертификат из хранилища сертификатов Windows
pdf.SetSignProcessCertFromStore(iSignProcessID, '3E8EF1F4E64543F308D9DB13B2C01E9CE54F8E3F', 0);
// Задаем информацию для подписи
pdf.SetSignProcessInfo(iSignProcessID,
...
    Я рассмотрел базовый набор функций библиотеки Foxit Quick PDF Library для работы с цифровой подписью в PDF документе и общий подход к работе с ними. С остальными функциями можно ознакомится в документации к библиотеке или ее исходных кодах.

Комментариев нет:

Отправить комментарий