Интеграция с государственной системой маркировки «Честный Знак» (ГИС МТ) — одна из наиболее частых задач для разработчиков 1С в последнее время. Процесс авторизации в True API «Честного Знака» построен на механизме двухфакторного подтверждения с использованием электронной цифровой подписи (ЭЦП). На практике этот процесс часто вызывает трудности: от непонятных редиректов веб-сервера до ошибок проверки криптографической подписи. В этой статье мы подробно разберем, как правильно реализовать весь цикл авторизации, исправить популярные ошибки и предоставим готовые примеры кода для различных методов реализации.
Прежде чем переходить к коду, давайте разберем логику работы API. Авторизация происходит в три этапа:
uuid) и случайную строку данных (data).data на нашей стороне с помощью сертификата ЭЦП организации. Подпись должна быть присоединенной (в формате PKCS#7 / CAdES-BES) и закодированной в Base64.simpleSignIn, передавая в теле JSON-объект с исходным uuid и нашей полученной подписью. В ответ система присылает JWT-токен, который мы будем использовать для всех последующих запросов.В самом начале работы многие разработчики сталкиваются с тем, что вместо JSON-ответа от сервера приходит HTML-страница с ошибкой 307 Temporary Redirect. Проанализируем ситуацию. Веб-сервер Nginx, установленный на стороне «Честного Знака», выполняет перенаправление по следующим причинам:
https://.https://markirovka.crpt.ru/api/v3/true-api/auth/simpleSignIn/ (со слэшем на конце) вернет ошибку 307. Правильный адрес должен оканчиваться строго на имя метода: https://markirovka.crpt.ru/api/v3/true-api/auth/simpleSignIn.У разработчиков часто возникает вопрос: почему исходная строка data была короткой, а после подписания она превратилась в огромный текст длиной в 5000–8000 символов? Пугаться этого не нужно — это абсолютно правильное поведение.
Дело в том, что «Честный Знак» требует именно присоединенную подпись. Такая подпись содержит не только криптографический хэш исходных данных, но и открытый ключ самого сертификата, сведения о владельце, цепочку доверия и прочую метаинформацию. Все это упаковывается в бинарный контейнер PKCS#7 и кодируется в Base64, что и увеличивает размер строки в разы. В примерах официальной документации ЧЗ часто приводят короткие фиктивные строки просто для наглядности структуры JSON.
Любые криптографические инструменты (будь то утилиты КриптоПро или COM-объекты) при экспорте результата в формат Base64 автоматически разбивают длинную строку на строки по 64 или 76 символов, разделяя их невидимыми символами перевода строки (\r\n). Если отправить данные в таком виде в JSON-запросе, сервер вернет ошибку разбора структуры пакета.
Перед отправкой подписи обязательно очищайте ее от всех служебных символов переноса строки и возврата каретки:
ПодписьОчищенная = СтрЗаменить(ПодписьBase64, Символы.ПС, "");
ПодписьОчищенная = СтрЗаменить(ПодписьОчищенная, Символы.ВК, "");
---
Если вы работаете на современной платформе 1С:Предприятие 8.3, самым надежным и переносимым решением будет использование встроенного объекта МенеджерКриптографии. Этот способ работает без использования внешних библиотек и одинаково хорошо применим как на клиенте, так и на сервере (если КриптоПро установлен на сервере 1С).
Рассмотрим по шагам программный код для подписания данных:
&НаСервере
Функция ПодписатьСтрокуВстроеннымиСредствами(СлучайнаяСтрока, ОтпечатокСертификата) Экспорт
// Инициализируем менеджер криптографии для ГОСТ Р 34.10-2012 (тип провайдера 75 или 80)
Менеджер = Новый МенеджерКриптографии("", "", 75);
// Ищем нужный сертификат в хранилище операционной системы по отпечатку
СертификатЭЦП = Неопределено;
СписокСертификатов = Менеджер.ПолучитьСписокСертификатов();
// Очищаем отпечаток от пробелов для точного сравнения
ПоискОтпечатка = ВРег(СтрЗаменить(ОтпечатокСертификата, " ", ""));
Для Каждого ТекСертификат Из СписокСертификатов Цикл
Если ВРег(ТекСертификат.Отпечаток) = ПоискОтпечатка Тогда
СертификатЭЦП = ТекСертификат;
Прервать;
КонецЕсли;
КонецЦикла;
Если СертификатЭЦП = Неопределено Тогда
ВызватьИсключение "Указанный сертификат ЭЦП не найден в личном хранилище системы!";
КонецЕсли;
// Преобразуем полученную из ЧЗ строку в двоичные данные в кодировке UTF-8
ДанныеДляПодписи = ПолучитьДвоичныеДанныеИзСтроки(СлучайнаяСтрока, КодировкаТекста.UTF8);
// Метод Подписать() платформы 1С по умолчанию создает присоединенную подпись PKCS#7
ДвоичныеДанныеПодписи = Менеджер.Подписать(ДанныеДляПодписи, СертификатЭЦП);
// Кодируем результат в формат Base64
ПодписьBase64 = ДвоичныеДанныеBase64(ДвоичныеДанныеПодписи);
// Очищаем полученную строку от переносов
ПодписьBase64 = СтрЗаменить(ПодписьBase64, Символы.ПС, "");
ПодписьBase64 = СтрЗаменить(ПодписьBase64, Символы.ВК, "");
Возврат ПодписьBase64;
КонецФункции
---
Если по каким-то причинам встроенный менеджер криптографии вам не подходит, либо вы ведете разработку на старых версиях платформы (включая 1С 7.7), отличной альтернативой станет использование COM-компоненты КриптоПро — CAdESCOM.
Разберем готовые функции для работы с объектами CAdESCOM.Store и CAdESCOM.CPSigner:
// Функция ищет сертификат в личном хранилище текущего пользователя по шестнадцатеричному отпечатку
Функция ПолучитьСертификатCAdESCOM(Отпечаток)
CAPICOM_CURRENT_USER_STORE = 2; // Искать в ветке "Текущий пользователь"
CAPICOM_MY_STORE = "My"; // Искать в папке "Личное"
CAPICOM_STORE_OPEN_READ_ONLY = 0; // Открывать только для чтения
CAPICOM_CERTIFICATE_FIND_SHA1_HASH = 0; // Поиск по SHA1 хэшу (отпечатку)
oStore = Новый COMОбъект("CAdESCOM.Store");
oStore.Open(CAPICOM_CURRENT_USER_STORE, CAPICOM_MY_STORE, CAPICOM_STORE_OPEN_READ_ONLY);
// Очищаем переданный отпечаток от возможных пробелов
ЧистыйОтпечаток = СтрЗаменить(Отпечаток, " ", "");
НайденныеСертификаты = oStore.Certificates.Find(CAPICOM_CERTIFICATE_FIND_SHA1_HASH, ЧистыйОтпечаток);
Если НайденныеСертификаты.Count = 0 Тогда
oStore.Close();
ВызватьИсключение "Сертификат с указанным отпечатком не найден!";
КонецЕсли;
ВыбранныйСертификат = НайденныеСертификаты.Item(1);
oStore.Close();
Возврат ВыбранныйСертификат;
КонецФункции
// Функция формирует присоединенную подпись в Base64 для переданного текста
Функция ПодписатьТекстCAdESCOM(ТекстДляПодписи, Отпечаток) Экспорт
CADESCOM_BASE64_TO_BINARY = 1; // Входные данные передаются в Base64 (или мы сконвертируем их предварительно)
CADESCOM_CADES_TYPE = 1; // Тип усовершенствованной подписи (CAdES-BES)
bDetached = Ложь; // Указываем Ложь, чтобы получить ПРИСОЕДИНЕННУЮ подпись
// Создаем объект параметров подписания
oSigner = Новый COMОбъект("CAdESCOM.CPSigner");
oSigner.Certificate = ПолучитьСертификатCAdESCOM(Отпечаток);
// Настраиваем объект данных
oSignedData = Новый COMОбъект("CAdESCOM.CadesSignedData");
oSignedData.ContentEncoding = CADESCOM_BASE64_TO_BINARY;
// Кодируем исходный текст в Base64 перед передачей в CAdESCOM
ВременныеДвоичныеДанные = ПолучитьДвоичныеДанныеИзСтроки(ТекстДляПодписи, КодировкаТекста.UTF8);
ТекстBase64 = ДвоичныеДанныеBase64(ВременныеДвоичныеДанные);
oSignedData.Content = ТекстBase64;
// Выполняем подписание
СтрокаПодписи = oSignedData.SignCades(oSigner, CADESCOM_CADES_TYPE, bDetached);
// Очищаем подпись от нежелательных переносов строк
СтрокаПодписи = СтрЗаменить(СтрокаПодписи, Символы.ПС, "");
СтрокаПодписи = СтрЗаменить(СтрокаПодписи, Символы.ВК, "");
Возврат СтрокаПодписи;
КонецФункции
---
В старых конфигурациях 1С или при отсутствии возможности зарегистрировать COM-объекты, разработчики часто прибегают к вызову консольной утилиты, поставляемой вместе с КриптоПро CSP — csptest.exe.
При использовании этого метода крайне важно учитывать два нюанса:
csptest.exe на некоторых системах при указании отпечатка может вызывать диалоговое окно выбора сертификата, в то время как 32-битная работает стабильно в фоновом режиме.Посмотрим на пример реализации вызова утилиты:
Функция ПодписатьЧерезCspTest(ИсходныеДанные, Отпечаток, КаталогВременныхФайлов)
ИмяФайлаВход = КаталогВременныхФайлов + "data_in.tmp";
ИмяФайлаВыход = КаталогВременныхФайлов + "data_out.tmp";
// Записываем исходные данные во временный файл
ТекстДок = Новый ТекстовыйДокумент;
ТекстДок.УстановитьТекст(ИсходныеДанные);
ТекстДок.Записать(ИмяФайлаВход, КодировкаТекста.UTF8);
// Формируем командную строку. Путь к csptest.exe обязательно оборачиваем в кавычки
ПутьККриптоПро = """C:\Program Files (x86)\Crypto Pro\CSP\csptest.exe""";
ЧистыйОтпечаток = СтрЗаменить(Отпечаток, " ", "");
ПараметрыЗапуска = " -sfsign -sign -in """ + ИмяФайлаВход + """ -out """ + ИмяФайлаВыход + """ -my " + ЧистыйОтпечаток + " -base64 -add";
ПолнаяКоманда = ПутьККриптоПро + ПараметрыЗапуска;
// Запускаем процесс синхронно, ожидая завершения (третий параметр равен Истина)
ПодключитьВнешнююКомпоненту("WScript.Shell");
Shell = Новый COMОбъект("WScript.Shell");
КодВозврата = Shell.Run(ПолнаяКоманда, 0, Истина);
Если КодВозврата <> 0 Тогда
ВызватьИсключение "Ошибка выполнения команды csptest.exe! Код ошибки: " + Строка(КодВозврата);
КонецЕсли;
// Считываем результат подписания
РезультатДок = Новый ТекстовыйДокумент;
РезультатДок.Прочитать(ИмяФайлаВыход, КодировкаТекста.UTF8);
СтрокаПодписи = РезультатДок.ПолучитьТекст();
// Очищаем полученный Base64 от переводов строк
СтрокаПодписи = СтрЗаменить(СтрокаПодписи, Символы.ПС, "");
СтрокаПодписи = СтрЗаменить(СтрокаПодписи, Символы.ВК, "");
// Удаляем временные файлы за собой
Попытка
УдалитьФайлы(ИмяФайлаВход);
УдалитьФайлы(ИмяФайлаВыход);
Исключение
КонецПопытки;
Возврат СтрокаПодписи;
КонецФункции
---
После того, как мы успешно получили строку подписи и очистили её от переносов, нам необходимо сформировать JSON-запрос и отправить его на сервер. Обратите внимание, что согласно актуальной спецификации True API «Честного Знака», отправлять данные нужно в виде одиночного JSON-объекта (квадратные скобки из старых версий документации использовать не нужно).
Пример формирования POST-запроса к API:
Функция ПолучитьТокенАвторизации(АдресСервера, UUIDСессии, ПодписьBase64)
// Формируем JSON-тело запроса
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ПараметрыЗапроса = Новый Структура;
ПараметрыЗапроса.Вставить("uuid", UUIDСессии);
ПараметрыЗапроса.Вставить("data", ПодписьBase64);
ЗаписатьJSON(ЗаписьJSON, ПараметрыЗапроса);
СтрокаЗапросаJSON = ЗаписьJSON.Закрыть();
// Инициализируем HTTP-соединение (не используйте слэш в конце адреса!)
// АдресСервера должен быть вида: "markirovka.crpt.ru"
НТТРСоединение = Новый HTTPСоединение(АдресСервера, 443, , , , , Новый ЗащищенноеСоединениеOpenSSL());
// Настраиваем POST-запрос
HTTPЗапрос = Новый HTTPЗапрос("/api/v3/true-api/auth/simpleSignIn");
HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/json; charset=UTF-8");
HTTPЗапрос.УстановитьТелоИзСтроки(СтрокаЗапросаJSON, КодировкаТекста.UTF8);
Попытка
HTTPОтвет = НТТРСоединение.ОтправитьДляОбработки(HTTPЗапрос);
КодСостояния = HTTPОтвет.КодСостояния;
ТелоОтвета = HTTPОтвет.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
Если КодСостояния = 200 Тогда
// Извлекаем токен из JSON-ответа сервера
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(ТелоОтвета);
ДанныеОтвета = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
Возврат ДанныеОтвета.token; // Наш искомый JWT-токен
Иначе
ВызватьИсключение "Ошибка авторизации ЧЗ! Код: " + Строка(КодСостояния) + ", Ответ: " + ТелоОтвета;
КонецЕсли;
Исключение
ВызватьИсключение "Не удалось соединиться с сервером Честного Знака: " + ОписаниеОшибки();
КонецПопытки;
КонецФункции
Мы полностью разобрали процесс авторизации в True API «Честного Знака». Используя встроенные методы платформы 1С или внешние криптографические компоненты, вы сможете надежно реализовать процесс получения токена без вылетов программы и досадных системных ошибок.