При интеграции 1С с системой «Честный Знак» (ГИС МТ) разработчики часто сталкиваются с ситуацией, когда авторизация проходит успешно, токен получается, но при попытке отправить документ (например, отгрузку или приемку) система возвращает ошибку «Не пройдена проверка подписи». Особенно остро эта проблема проявляется после плановой смены сертификата электронной подписи (ЭЦП). Даже если подпись корректно работает в личном кабинете через браузер, API-запросы могут отклоняться из-за строгих требований к формату передаваемых данных и самой подписи.
В этой статье мы подробно разберем технические нюансы формирования подписи для Честного Знака, проанализируем примеры кода и выясним, как правильно подготовить данные для успешной проверки на стороне сервера ГИС МТ.
Рассмотрим основные причины, по которым новая подпись может не проходить проверку через программный интерфейс:
HTTPЗапрос передали ее же, но с добавлением лишнего пробела или переноса строки, хеш-сумма не совпадет, и подпись будет признана невалидной.Base64 (особенно в старых версиях или при определенных настройках) может вставлять символы переноса строк (CRLF) каждые 72 символа. Честный Знак ожидает «чистую» строку без этих символов.Прежде чем вызывать функции криптографии, проанализируем процесс подготовки JSON. Важно помнить, что подписываться должен именно тот текст, который будет отправлен в поле product_document. Рассмотрим, как правильно очистить строку от «мусора», который может добавить 1С.
Для версий 1С:Предприятие 8.2 и некоторых конфигураций на 8.3 может потребоваться самописная функция очистки символов переноса строки. Выясним, как она выглядит:
Функция УбратьСимволы1013(СтрокаДляОчистки) Экспорт
// Удаляем символы перевода строки (ПС) и возврата каретки (ВК)
Результат = СтрЗаменить(СтрокаДляОчистки, Символы.ПС, "");
Результат = СтрЗаменить(Результат, Символы.ВК, "");
// В некоторых случаях 1С может добавлять BOM (Byte Order Mark) в начало строки
// Если это происходит, его также необходимо удалить
Возврат Результат;
КонецФункции
Проанализируем ситуацию: если вы используете метод Base64Строка(), обязательно пропускайте результат через подобную очистку, прежде чем вставлять его в итоговый пакет данных.
Для программной работы с подписью нам необходимо найти нужный сертификат по его отпечатку (Thumbprint). Разберем по шагам, как это реализовать через COM-объект CAdESCOM.Store. Это наиболее универсальный способ, работающий в операционных системах Windows с установленным КриптоПро CSP.
Функция ПолучитьСертификатПоОтпечатку(ОтпечатокСтр)
Рез = Неопределено;
// Константы для работы с CAPICOM/CAdESCOM
CAPICOM_CURRENT_USER_STORE = 2; // Хранилище текущего пользователя
CAPICOM_MY_STORE = "My"; // Ветка "Личное"
CAPICOM_STORE_OPEN_READ_ONLY = 0;
Попытка
oStore = Новый COMОбъект("CAdESCOM.Store");
oStore.Open(CAPICOM_CURRENT_USER_STORE, CAPICOM_MY_STORE, CAPICOM_STORE_OPEN_READ_ONLY);
// Поиск сертификата по SHA1 хешу (отпечатку)
CAPICOM_CERTIFICATE_FIND_SHA1_HASH = 0;
НаденныеСертификаты = oStore.Certificates.Find(CAPICOM_CERTIFICATE_FIND_SHA1_HASH, ОтпечатокСтр);
Если НаденныеСертификаты.Count > 0 Тогда
Рез = НаденныеСертификаты.Item(1);
КонецЕсли;
oStore.Close();
Исключение
Сообщить("Ошибка при доступе к хранилищу сертификатов: " + ОписаниеОшибки());
КонецПопытки;
Возврат Рез;
КонецФункции
Теперь перейдем к самому ответственному этапу — созданию открепленной подписи. Рассмотрим пример функции, которая принимает текст в формате Base64, отпечаток сертификата и возвращает готовую подпись.
Важный момент: Обратите внимание на свойство ContentEncoding. Если данные передаются в Base64, нужно установить значение 1 (CADESCOM_BASE64_TO_BINARY), чтобы объект понимал, что текст нужно сначала декодировать перед хешированием.
Функция ПодписатьТекст(ТекстДляПодписи, sThumbprint, bDetached = Истина)
// Константы типа подписи
CADESCOM_CADES_TYPE = 1; // CAdES-BES
CADESCOM_BASE64_TO_BINARY = 1; // Входные данные в Base64
CAPICOM_AUTHENTICATED_ATTRIBUTE_SIGNING_TIME = 0; // Атрибут времени
Попытка
oSigner = Новый COMОбъект("CAdESCOM.CPSigner");
oSigner.Certificate = ПолучитьСертификатПоОтпечатку(sThumbprint);
// Добавляем обязательный атрибут времени подписания
oSigningTimeAttr = Новый COMОбъект("CAdESCOM.CPAttribute");
oSigningTimeAttr.Name = CAPICOM_AUTHENTICATED_ATTRIBUTE_SIGNING_TIME;
oSigningTimeAttr.Value = ТекущаяДата();
oSigner.AuthenticatedAttributes2.Add(oSigningTimeAttr);
oSignedData = Новый COMОбъект("CAdESCOM.CadesSignedData");
oSignedData.ContentEncoding = CADESCOM_BASE64_TO_BINARY;
oSignedData.Content = СокрЛП(ТекстДляПодписи);
// Формируем подпись
// EncodingType = 0 означает, что результат (подпись) будет в Base64
EncodingType = 0;
sSignedMessage = oSignedData.SignCades(oSigner, CADESCOM_CADES_TYPE, bDetached, EncodingType);
Возврат УбратьСимволы1013(sSignedMessage);
Исключение
Сообщить("Ошибка при формировании подписи: " + ОписаниеОшибки());
Возврат "";
КонецПопытки;
КонецФункции
Рассмотрим завершающий этап: формирование тела запроса и отправку его через HTTPСоединение. Мы будем использовать структуру запроса для создания документа отгрузки (Shipment).
Процедура ОтправитьДокументВЧестныйЗнак(JSONДокумента, ОтпечатокСертификата, ТокенАвторизации)
// 1. Кодируем сам документ в Base64 и очищаем от переносов
СтрокаДокументаBase64 = УбратьСимволы1013(Base64Строка(ПолучитьДвоичныеДанныеИзСтроки(JSONДокумента, "UTF-8", Ложь)));
// 2. Подписываем полученную Base64-строку
ПодписьBase64 = ПодписатьТекст(СтрокаДокументаBase64, ОтпечатокСертификата, Истина);
// 3. Формируем структуру пакета для API
Пакет = Новый Структура;
Пакет.Вставить("product_document", СтрокаДокументаBase64);
Пакет.Вставить("document_format", "MANUAL"); // Или XML, в зависимости от задачи
Пакет.Вставить("type", "LP_SHIP_GOODS");
Пакет.Вставить("signature", ПодписьBase64);
// Преобразуем пакет в JSON для тела запроса
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, Пакет);
ТелоЗапроса = ЗаписьJSON.Закрыть();
// 4. Настройка соединения
Сервер = "ismp.crpt.ru"; // Адрес может меняться для разных товарных групп
АдресРесурса = "/api/v3/lk/documents/shipment/create";
Соединение = Новый HTTPСоединение(Сервер, 443, , , , , Новый ЗащищенноеСоединениеOpenSSL());
Запрос = Новый HTTPЗапрос(АдресРесурса);
Запрос.Заголовки.Вставить("Content-Type", "application/json");
Запрос.Заголовки.Вставить("Authorization", "Bearer " + ТокенАвторизации);
Запрос.УстановитьТелоИзСтроки(ТелоЗапроса);
Попытка
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Если Ответ.КодСостояния = 200 Тогда
Сообщить("Документ успешно отправлен. Идентификатор: " + Ответ.ПолучитьТелоКакСтроку());
Иначе
Сообщить("Ошибка сервера: " + Ответ.КодСостояния);
Сообщить(Ответ.ПолучитьТелоКакСтроку());
КонецЕсли;
Исключение
Сообщить("Ошибка связи: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Если после внедрения кода ошибка сохраняется, проанализируем следующие моменты:
SigningTime.product_document приведет к ошибке проверки подписи.Таким образом, успех интеграции зависит от строгого соблюдения последовательности: формирование компактного JSON — кодирование в Base64 — очистка строки от переносов — создание открепленной подписи CAdES-BES с атрибутом времени — упаковка в финальный JSON-контейнер. Посмотрев на приведенные примеры кода, вы сможете адаптировать их под свою конфигурацию 1С и успешно решить проблему с проверкой подписи. Для решения похожих задач можно использовать обработку для подписи файлов сертификатом ЭЦП средствами 1С.