При реализации автоматической отправки платежных поручений через сервис DirectBank разработчики часто сталкиваются с задачей предварительной валидации: вставлен ли физический токен с электронной подписью в компьютер и доступен ли закрытый ключ. Попытка проверить доступность закрытого ключа путем тестового подписания фрагмента данных для каждого документа в цикле нередко приводит к существенным задержкам. Время выполнения процедуры может варьироваться от нескольких секунд до нескольких минут, что неприемлемо для регламентных и фоновых обработчиков.
Разберем детально, почему штатная проверка работает медленно, как оптимизировать алгоритм встроенного языка 1С и какие альтернативные способы проверки контейнеров существуют.
Проанализируем исходный фрагмент кода, где для каждого платежного поручения выполняется тестовая подпись:
Для Каждого ДокументПлатежки Из СписокДокументов Цикл
Если ПроверкаСертификатаНаДоступность(ДокументПлатежки) Тогда
СписокДокументовНаОтправку.Добавить(ДокументПлатежки);
КонецЕсли;
КонецЦикла;
В функции проверки на каждой итерации создается новый объект МенеджерКриптографии и вызывается метод Подписать(). Выделим основные узкие места такого подхода:
МенеджерКриптографии внутри итератора заставляет операционную систему заново инициализировать библиотеки криптопровайдера КриптоПро CSP и открывать системные дескрипторы для каждого платежного поручения.В платформе 1С объект СертификатКриптографии содержит исключительно данные открытого ключа. Прямого платформенного метода вроде «ПроверитьНаличиеКонтейнера()» не существует, поэтому факт наличия закрытого ключа подтверждается только через попытку криптографической операции. Однако проверять один и тот же сертификат на каждую платежку избыточно.
Рассмотрим, как правильно реорганизовать логику: соберем уникальные сертификаты перед отправкой, проверим закрытый ключ однократно для каждого сертификата и сохраним результат в структуру или соответствие:
&НаКлиенте
Процедура АвтоПодписаниеБанка() Экспорт
ОтключитьОбработчикОжидания("АвтоПодписаниеБанка");
Попытка
СписокДокументов = ДО_СерверДиректБанк.ПолучитьСписокДокументовДляОтправкиВБанкПоДиректБанку();
Если СписокДокументов.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Инициализируем менеджер криптографии один раз за пределами циклов
Менеджер = Новый МенеджерКриптографии("", "", 80);
// Соответствие: Ключ - СертификатСсылка/Данные, Значение - Булево (Доступен)
КэшДоступностиСертификатов = Новый Соответствие;
СписокДокументовНаОтправку = Новый Массив;
Для Каждого ДокументПлатежки Из СписокДокументов Цикл
Сертификат = итс_ОбщийМодульСервер.ПолучениеСертификатаИзОбменаСБанком(ДокументПлатежки);
Если Не ЗначениеЗаполнено(Сертификат) Тогда
Продолжить;
КонецЕсли;
Доступен = КэшДоступностиСертификатов[Сертификат];
Если Доступен = Неопределено Тогда
ДанныеСертификата = итс_ОбщийМодульСервер.ПолучениеДанныхСертификатаИзОбменаСБанком(ДокументПлатежки);
КриптоСертификат = Новый СертификатКриптографии(ДанныеСертификата);
ПарольКлюча = ПроверкаСертификатаНаПарольНаСервере(Сертификат);
Менеджер.ПарольДоступаКЗакрытомуКлючу = ПарольКлюча;
// Проверяем токен один раз для всей группы документов
Доступен = ПроверитьТокенБыстро(Менеджер, КриптоСертификат);
КэшДоступностиСертификатов.Вставить(Сертификат, Доступен);
КонецЕсли;
Если Доступен = Истина Тогда
СписокДокументовНаОтправку.Добавить(ДокументПлатежки);
КонецЕсли;
КонецЦикла;
// Пакетная отправка документов вместо поштучной
Если СписокДокументовНаОтправку.Количество() > 0 Тогда
итс_ОбщийМодульСервер.СогласоватьДокумент(СписокДокументовНаОтправку);
ОбменСБанкамиКлиент.СформироватьПодписатьОтправитьЭД(СписокДокументовНаОтправку);
КонецЕсли;
Исключение
итс_ОбщийМодульСервер.ИТСЗаписатьОшибкуВЖурнал(ОписаниеОшибки());
КонецПопытки;
ПодключитьОбработчикОжидания("АвтоПодписаниеБанка", 30);
КонецПроцедуры
&НаКлиенте
Функция ПроверитьТокенБыстро(Менеджер, КриптоСертификат)
ТекстТеста = ПолучитьДвоичныеДанныеИзСтроки("1");
Попытка
Менеджер.Подписать(ТекстТеста, КриптоСертификат, "");
Возврат Истина;
Исключение
Возврат Ложь;
КонецПопытки;
КонецФункции
Обратим внимание на вызов процедуры отправки: передача всего массива СписокДокументовНаОтправку в метод ОбменСБанкамиКлиент.СформироватьПодписатьОтправитьЭД() позволяет типовой библиотеке обмена с банками подписать весь пакет за одну сессию обращения к криптопровайдеру без лишних вызовов.
В большинстве сценариев автоматизации предварительная тестовая подпись является избыточной операцией. Метод ОбменСБанкамиКлиент.СформироватьПодписатьОтправитьЭД() внутри себя уже содержит все необходимые обработчики ошибок криптографии.
Разберем логику прямого подписания:
Попытка ... Исключение, фиксируем статус в журнале регистрации и повторяем попытку по расписанию через обработчик ожидания.Такой подход сокращает количество криптографических операций ровно в два раза.
Если требуется строго проверить фактическое физическое присутствие контейнера в системе без выполнения операции вычисления электронной подписи и без обращения к сетевым спискам отзыва, можно использовать консольную утилиту csptest.exe, входящую в дистрибутив КриптоПро CSP.
Посмотрим на пример логики опроса считывателей через командную строку Windows:
Функция ПроверитьНаличиеКонтейнераЧерезCsptest(ИмяКонтейнера) Экспорт
ПутьУтилиты = "C:\Program Files\Crypto Pro\CSP\csptest.exe";
КаталогВременный = КаталогВременныхФайлов();
ФайлРезультата = КаталогВременный + "csptest_out.txt";
Команда = СтрШаблон("""%1"" -keyset -enum_cont -verifycontext -fqcn > ""%2""", ПутьУтилиты, ФайлРезультата);
ЗапуститьПриложение(Команда, , Истина);
ФайлОтвета = Новый Файл(ФайлРезультата);
Если Не ФайлОтвета.Существует() Тогда
Возврат Ложь;
КонецЕсли;
Чтение = Новый ЧтениеТекста(ФайлРезультата, КодировкаТекста.OEM);
ТекстВывода = Чтение.Прочитать();
Чтение.Закрыть();
УдалитьФайлы(ФайлРезультата);
// Проверяем вхождение имени контейнера в вывод списка считывателей
Возврат (Найти(ТекстВывода, ИмяКонтейнера) > 0);
КонецФункции
Команда csptest.exe -keyset -enum_cont -verifycontext -fqcn быстро перечисляет полностью квалифицированные имена всех найденных контейнеров во всех подключенных считывателях без вычисления хешей и сетевых запросов. Если имя целевого контейнера найдено в списке — носитель физически подключен к рабочей станции.
Чтобы исключить сетевые зависания при операциях проверки подписи, выполним несколько сервисных настроек операционной системы: