Перед многими разработчиками и системными архитекторами встает задача организации внутреннего электронного документооборота (ЭДО). Типичный сценарий: необходимо перевести внутренний документ (например, Акт списания или Перемещение ТМЦ) в формат PDF, подписать его неквалифицированной электронной подписью (НЭП) на стороне сервера и визуализировать эту подпись в виде красивого штампа непосредственно в теле документа. При этом бизнес часто ставит жесткое условие — решение должно быть максимально бюджетным или полностью бесплатным, без покупки серверных лицензий «КриптоПро CSP» (стоимость которых для сервера может быть весьма существенной).
В этой статье мы подробно разберем, как реализовать этот механизм, проанализируем различные подходы, рассмотрим готовые примеры кода на стороне 1С и обсудим важные архитектурные нюансы безопасности при подписании документов через СМС-подтверждение.
---
Начнем с самого современного и элегантного способа. Если ваша конфигурация работает на платформе 1С:Предприятие версии 8.3.16 или старше, вам больше не требуются сторонние утилиты для работы с PDF. Фирма 1С добавила мощный встроенный механизм для работы с PDF-документами, который позволяет программно добавлять подписи и визуальные штампы.
Для реализации этого подхода используются системные объекты ЗаписьPDF, ОписаниеПодписиPDF и МенеджерКриптографии. Давайте разберем по шагам, как настроить бесплатное серверное подписание на Windows с использованием стандартных криптопровайдеров ОС (без КриптоПро).
Посмотрим на пример кода, реализующий серверное подписание PDF-файла:
// Функция осуществляет подписание PDF файла встроенными средствами платформы
Функция ПодписатьPDFНаСервере(ПутьКФайлуИсточник, ПутьКФайлуРезультат, ИмяСертификатаВХранилище) Экспорт
// Инициализируем менеджер криптографии.
// Используем стандартный системный криптопровайдер Windows (RSA), который бесплатен.
МенеджерКрипт = Новый МенеджерКриптографии("", "", 1); // 1 - тип провайдера PROV_RSA_FULL
// Поиск необходимого сертификата в системном хранилище "My" (Личные) на сервере
ВыбранныйСертификат = Неопределено;
Сертификаты = МенеджерКрипт.ПолучитьСертификатыИзХранилища("My");
Для Каждого Сертификат Из Сертификаты Цикл
Если Найти(Сертификат.Субъект, ИмяСертификатаВХранилище) > 0 Тогда
ВыбранныйСертификат = Сертификат;
Прервать;
КонецЕсли;
КонецЦикла;
Если ВыбранныйСертификат = Неопределено Тогда
ВызватьИсключение "Не найден подходящий сертификат в хранилище сервера!";
КонецЕсли;
// Настраиваем параметры визуального отображения штампа подписи
ОписаниеПодписи = Новый ОписаниеПодписиPDF;
ОписаниеПодписи.Имя = "ПодписьБухгалтера";
ОписаниеПодписи.Страница = 1; // Номер страницы для размещения штампа (или ОписаниеПодписиPDF.ПоследняяСтраница)
// Задаем координаты расположения штампа в пикселях
ОписаниеПодписи.Лево = 50;
ОписаниеПодписи.Низ = 100;
ОписаниеПодписи.Ширина = 250;
ОписаниеПодписи.Высота = 90;
// Создаем визуальный макет штампа. В качестве макета можно использовать Табличный Документ 1С.
ТабДокШтамп = Новый ТабличныйДокумент;
// ... здесь можно программно заполнить ячейки макета: нарисовать рамку, вывести ФИО, дату, текст "ДОКУМЕНТ ПОДПИСАН" ...
Область = ТабДокШтамп.Область("R1C1:R5C5");
Область.Текст = "ДОКУМЕНТ ПОДПИСАН ЭП" + Символы.ПС + ВыбранныйСертификат.Субъект;
Область.Обвести();
ОписаниеПодписи.Отображение = ТабДокШтамп;
// Производим чтение исходного файла и запись нового с наложением подписи
ЗаписьPDF = Новый ЗаписьPDF;
ЗаписьPDF.Открыть(ПутьКФайлуРезультат);
// Передаем исходный документ, сертификат и настройки подписи
ЗаписьPDF.Записать(ПутьКФайлуИсточник, ВыбранныйСертификат, ОписаниеПодписи);
ЗаписьPDF.Закрыть();
Возврат Истина;
КонецФункции
Преимущества данного способа: Полная автономность от стороннего ПО, отсутствие затрат на лицензии при работе на Windows (используется встроенный CryptoAPI), высокая скорость генерации, штамп встраивается непосредственно в структуру PDF.
---
Если сервер 1С работает под управлением Linux или вам требуется гибко управлять сложными визуальными слоями (например, «склеивать» несколько документов, динамически накладывать факсимиле подписи и печати поверх готового текста в строго определенные места), отличным решением будет использование консольной утилиты PDFTK (PDF Toolkit).
Суть метода заключается в следующем: 1С генерирует отдельный одностраничный PDF-файл, содержащий только графический штамп подписи на прозрачном фоне в нужных координатах. Затем с помощью утилиты PDFTK этот штамп накладывается в качестве фонового рисунка (водяного знака) на целевую страницу основного документа.
Рассмотрим пример реализации этой схемы на стороне сервера 1С:
Процедура НаложитьШтампЧерезPDFTK(ПутьКИсходномуPDF, ПутьКФайлуШтампа, ПутьКГотовомуPDF) Экспорт
// Задаем путь к исполняемому файлу утилиты pdftk на сервере
ПутьКПрограмме = """C:\Program Files (x86)\PDFtk Server\bin\pdftk.exe""";
// Формируем параметры запуска. Команда "background" накладывает один PDF на другой как подложку.
// Если нужно наложить поверх текста, используется команда "stamp".
ПараметрыЗапуска = СтрШаблон("%1 ""%2"" stamp ""%3"" output ""%4""",
ПутьКПрограмме,
ПутьКИсходномуPDF,
ПутьКФайлуШтампа,
ПутьКГотовомуPDF);
// Запускаем консольное приложение в скрытом режиме
ЗапускПриложения(ПараметрыЗапуска, , Истина); // Третий параметр Истина означает ожидание завершения процесса
// Проверяем результат
ФайлРезультата = Новый Файл(ПутьКГотовомуPDF);
Если Не ФайлРезультата.Существовать() Или ФайлРезультата.Размер() = 0 Тогда
ВызватьИсключение "Ошибка генерации PDF: утилита PDFTK завершила работу некорректно.";
КонецЕсли;
КонецПроцедуры
Для реализации более сложной логики (например, когда документ содержит несколько страниц, а штамп нужно поставить только на последней), можно программно разделять исходный документ на страницы, накладывать штамп на последнюю и склеивать обратно. Разберем шаблон такой процедуры:
Процедура ПолучитьФайлСПодписями(НакопленныеДанные)
Попытка
ОпределитьВозможностьСклейкиСПодписями(НакопленныеДанные);
Если НакопленныеДанные.МожноСклеить = Истина Тогда
ПолучитьИзначальныйФайл(НакопленныеДанные);
ПодготовитьСредствоРаботыСPDF(НакопленныеДанные);
СгенерироватьФайлСлужебнойИнформации(НакопленныеДанные);
ПолучитьИнтересующиеДанныеИзФайлаСлужебнойИнформации(НакопленныеДанные);
СоздатьPDFСНумерацией(НакопленныеДанные);
СоздатьPDFСПодписями(НакопленныеДанные);
СклеитьФайлы(НакопленныеДанные);
УдалитьВременныеФайлы(НакопленныеДанные);
КонецЕсли;
Исключение
Сообщить("Ошибка выполнения операции с PDFTK: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
---
Когда сервер 1С функционирует на операционной системе Linux, работа со стандартными Windows-криптопровайдерами невозможна, а установка КриптоПро требует затрат. В таких случаях идеальной архитектурной альтернативой является создание локального микросервиса на вашем сервере приложений.
Отличным решением будет развертывание небольшого веб-сервиса на Python с использованием библиотеки pyHanko (современный инструмент для создания PDF-подписей по стандарту PAdES) или библиотеки endesive. Сервер 1С будет отправлять по HTTP POST исходный файл PDF и файл сертификата (в формате PFX / P12 с паролем), а в ответ получать готовый подписанный PDF со штампом.
Примерная схема HTTP-запроса из 1С к такому сервису выглядит так:
Функция ОтправитьНаСервисПодписания(ПутьКФайлуPDF, ПутьКСертификатуPFX, ПарольКСертификату, АдресСервиса)
// Читаем файлы в двоичные данные
ДвоичныеДанныеPDF = Новый ДвоичныеДанные(ПутьКФайлуPDF);
ДвоичныеДанныеPFX = Новый ДвоичныеДанные(ПутьКСертификатуPFX);
// Формируем POST-запрос (Multipart form-data или JSON с кодированием в Base64)
Разделитель = "----Boundary" + Формат(ТекущаяДата(), "ДФ=yyyyMMddhhmmss");
ТелоЗапроса = Новый ТекстовыйДокумент;
// ... формируем тело запроса, содержащее PDF и PFX файлы ...
Запрос = Новый HTTPЗапрос(АдресСервиса);
Запрос.УстановитьТелоИзДвоичныхДанных(ТелоЗапроса.ПолучитьДвоичныеДанные());
Запрос.Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Разделитель);
Соединение = Новый HTTPСоединение("localhost", 8080); // Порт нашего микросервиса
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Если Ответ.КодСостояния = 200 Тогда
// Записываем полученный подписанный файл обратно на диск
Ответ.ПолучитьТелоИзДвоичныхДанных().Записать(ПутьКФайлуPDF);
Возврат Истина;
Иначе
ЗаписьЖурналаРегистрации("Подписание", УровеньЖурналаРегистрации.Ошибка, , , Ответ.ПолучитьТелоКакСтроку());
Возврат Ложь;
EndIf;
КонецФункции
---
Разберем важный методологический аспект, затронутый в обсуждении. При проектировании схемы «Пользователь нажимает кнопку в браузере -> Получает СМС -> Вводит код в 1С -> Сервер подписывает документ ключом, хранящимся на сервере» часто совершается критическая ошибка безопасности.
Проблема: Пользователь не контролирует, что именно он подписывает. В момент ввода СМС-кода сервер может теоретически подсунуть под подпись измененную версию PDF-файла (например, с другими суммами или реквизитами контрагента).
Чтобы исключить юридические риски и гарантировать неизменность документа в момент подписания, настоятельно рекомендуется использовать следующую схему:
Используя предложенные подходы, вы сможете развернуть стабильный, юридически значимый внутренний электронный документооборот на сервере 1С с красивыми визуальными штампами подписей абсолютно бесплатно.