Коллеги, всем добрый день! Работаю над интеграцией с системой ЭДО Лайт (Честный знак). Задача стоит в получении входящих документов и подписи их. Наработки взял отсюда https://infostart.ru/public/1276725/.
С получением токена, подключением, получением списка входящих документов проблем нет. Проблема как раз с подписью документов. На скриншоте указан алгоритм, но я получаю ошибку Header length exceeded maximum header size of 4096, код возврата - 413. Кто сталкивался? В чем может быть проблема? Спасибо за ответы.
Ниже алгоритм подписи:
1. Получить список документов, там будет id входящего документа /api/v1/incoming-documents.
2. Получить XML содержимое документа /api/v1/incoming-documents/{doc_id}/content
3. Создать и отправить титул покупателя, где <ЭП>signature</ЭП> это подписанное открепленной подписью полученное XML содержимое документа.(для УПД api/v1/incoming-documents/xml/upd/title). Метод вернет event_id или теперь его можно получить через список квитанций(/api/v1/incoming-documents/unsigned-events).
4. И теперь уже подписываете /api/v1/incoming-documents/{doc_id}/events/{event_id}/signature
Вот на 3 пункте я и остановился.
Где ФайлXML - текстовое содержимое файла XML внутреннего документа
doc_id - id внутреннего документа в ЭДО Лайт
Токен - собственно токен ЭДО Лайт
(1) В multipart/form-data данные от заголовков должны отделяться двойным переносом строк:
Р = "---------------------------"+Новый УникальныйИдентификатор();
ПС = Символы.ВК+Символы.ПС;
Заголовки.Вставить("Content-Type","multipart/form-data; boundary="+Р);
Тело = "";
Тело = Тело + "--"+Р+ПС;
Тело = Тело + "Content-Disposition: form-data; name=""content""; filename="""+УПД.ИДФайл+".xml"""+ПС;
Тело = Тело + "Content-Type: application/xml"+ПС+ПС;
Тело = Тело + ПолучитьСтрокуИзДвоичныхДанных(УПД.ДвоичныеДанные,КодировкаТекста.ANSI)+ПС;
Тело = Тело + "--"+Р+ПС;
Тело = Тело + "Content-Disposition: form-data; name=""signature"""+ПС;
Тело = Тело + "Content-Type: text/plain"+ПС+ПС;
Тело = Тело + Base64_Строка(Подпись) + ПС;
Тело = Тело + "--"+Р+"--"+ПС;
Соединение = Новый HTTPСоединение(ИмяСервера,,,,,,Новый ЗащищенноеСоединениеOpenSSL);
Запрос = Новый HTTPЗапрос("/api/v1/outgoing-documents",Заголовки);
Запрос.УстановитьТелоИзДвоичныхДанных(ПолучитьДвоичныеДанныеИзСтроки(Тело,КодировкаТекста.ANSI));
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
(1) В multipart/form-data данные от заголовков должны отделяться двойным переносом строк:
Р = "---------------------------"+Новый УникальныйИдентификатор();
ПС = Символы.ВК+Символы.ПС;
Заголовки.Вставить("Content-Type","multipart/form-data; boundary="+Р);
Тело = "";
Тело = Тело + "--"+Р+ПС;
Тело = Тело + "Content-Disposition: form-data; name=""content""; filename="""+УПД.ИДФайл+".xml"""+ПС;
Тело = Тело + "Content-Type: application/xml"+ПС+ПС;
Тело = Тело + ПолучитьСтрокуИзДвоичныхДанных(УПД.ДвоичныеДанные,КодировкаТекста.ANSI)+ПС;
Тело = Тело + "--"+Р+ПС;
Тело = Тело + "Content-Disposition: form-data; name=""signature"""+ПС;
Тело = Тело + "Content-Type: text/plain"+ПС+ПС;
Тело = Тело + Base64_Строка(Подпись) + ПС;
Тело = Тело + "--"+Р+"--"+ПС;
Соединение = Новый HTTPСоединение(ИмяСервера,,,,,,Новый ЗащищенноеСоединениеOpenSSL);
Запрос = Новый HTTPЗапрос("/api/v1/outgoing-documents",Заголовки);
Запрос.УстановитьТелоИзДвоичныхДанных(ПолучитьДвоичныеДанныеИзСтроки(Тело,КодировкаТекста.ANSI));
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
(2) У нас по сути похожие задачи. Только Вы не передаете doc_id. такой вопрос:
1. УПД.ИДФайл \то у Вас путь к файлу?
2. ПолучитьСтрокуИзДвоичныхДанных(УПД.ДвоичныеДанные,КодировкаТекста.ANSI) - это строковое представление XML файла?
(5)
1. УПД.ИДФайл - это идентификатор файла (см. Приказ ФНС России от 19.12.2018 N ММВ-7-15/820)
2. ПолучитьСтрокуИзДвоичныхДанных() - сам файл в виде строки (см. справку 1С)
3. В HTTP символы перевода строки отделяют одни данные от других (например, заголовки от содержимого). С этим нужно быть очень внимательным. Поэтому:
4. Не используйте ТекстовойДокумент, т.к. метод ПолучитьТекст() заменит символ ПС на свойство РазделительСтрок, а символ ВК не тронет. В результате получите ВК ПС ПС (или что-то подобное). Вот сервер и не распознаёт ваш multipart/form-data.
5. Не используйте кодировку UTF8. В том же приказе ФНС России указана кодировка "windows-1251". Используйте ANSI.
6. Не используйте УстановитьТелоИзСтроки() - опять же дополнительная перекодировка. Зачем? Есть готовая строка, которую нужно установить в качестве тела: Запрос.УстановитьТелоИзДвоичныхДанных(ПолучитьДвоичныеДанныеИзСтроки(Тело,КодировкаТекста.ANSI)) - без перекодировки, без добавления лишних символов.
Учел Ваши рекомендации но все равно ошибка DOC-0017: "Неверный тип документа загружаемого xml файла" (но уже другая, без Content-Type).
Такой вопрос: Я получаю содержимое XML файла, используя запрос "/api/v1/incoming-documents/{doc_id}/content".Потом это содержимое сохраняю в файл уже с расширением XML (используя ЗаписьТекста). Имя файла, например, получается такое:
C:\Users\Tobaco\AppData\Local\Temp\14\\ON_NSCHFDOPPRMARK_2LT-11000066004_2BE01b912a4ec954c309ef08dc3df19cacd_20220504_ca72975e-a5c0-410c-9963-8b0e66e612db.xml
ПутьКФайлу = СтрШаблон("%1\%2.xml",КаталогВременныхФайлов(),ИдФайл);
Текст = Новый ЗаписьТекста(ПутьКФайлу);
Текст.ЗаписатьСтроку(ОтветСтрока);
Текст.Закрыть();
И уже сам путь файла и его содержимое добавляю в тело запроса. Везде кодировка ANSI, символы ПС везде учел.
Такое ощущение, что Честный Знак думает, что я ему xml не УПД подсовываю, а что - то другое
В чем может быть загвоздка? Буду благодарен за помощь. Спасибо.
Сейчас формирую титул покупателя и возникла пара вопросов:
1. ИдФайл="ON_NSCHFDOPPOKMARK_1LT-10000000137_1LT-10000000138_20201119_2afedde7-f948-4a26-a2ed-d3dbcbf5e2fe" (взято из API). Мы формируем из данных XML файла УПД : "ON_NSCHFDOPPOKMARK_" + ИдПол_ + Что то не понятное с датой+ какой то гуид? Если есть информация
2. Во вложенном скрине не понятно, откуда, брать данные.
Если есть какая - то информация, можете сообщить? Или может где почитать.... Просто в описании API просто приведен пример и все, больше ничего.
3. Имя файла нужно передавать без указания пути. Зачем им на сервере ваш локальный путь? :)
Доброе утро! Спасибо. Файл сформировал, при отправке получил ошибку - "Ид в xml файле и auth токене не совпадают". Это что за ИД имеется ввиду? Спасибо.
Доброе утро! Спасибо. Файл сформировал, при отправке получил ошибку - "Ид в xml файле и auth токене не совпадают". Это что за ИД имеется ввиду? Спасибо.
Ниже пример, как я это делаю.
1. Ключ сессии (/api/v1/session) получаю вполне корректно, Честный знак принимает подпись
2. Входящие документы получаю корректно, но при попытке подписи документов (api/v1/incoming-documents/xml/upd/title) ошибка "Подпись не прошла проверку в crypto" (хотя при получении ключа сессии все нормально)
ПодписатьТекст(ЗашифроватьBase64(ФайлXML, "windows-1251"),Отпечаток,Истина);
// sThumbprint - отпечаток сертификата, используемого для подписи; строка,
// представляющая отпечаток в шестнадцатеричном виде
// пример 195934d72dcdf69149901d6632aca4562d8806d8
// ТекстДляПодписи должен быть в Base64
// bDetached - Истина/Ложь - откреплённая(для подписания документов)/прикреплённая(для получения токена авторизации) подпись
Функция ПодписатьТекст(ТекстДляПодписи, sThumbprint, bDetached)
CADESCOM_BASE64_TO_BINARY = 1; // Входные данные пришли в Base64
CADESCOM_CADES_TYPE = 1; // Тип усовершенствованной подписи
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");
// Объект CadesSignedData предоставляет свойства и методы для работы с усовершенствованной подписью.
oSignedData.ContentEncoding = CADESCOM_BASE64_TO_BINARY;
//oSignedData.propset_ContentEncoding = CADESCOM_BASE64_TO_BINARY;
oSignedData.Content = СокрЛП(ТекстДляПодписи);
EncodingType = 0;
sSignedMessage = oSignedData.SignCades(oSigner, CADESCOM_CADES_TYPE,
bDetached, EncodingType);
// Метод добавляет к сообщению усовершенствованную подпись.
sSignedMessage = СтрЗаменить(sSignedMessage,Символы.ПС,"");
sSignedMessage = СтрЗаменить(sSignedMessage,Символы.ВК,"");
Возврат sSignedMessage; // Подпись в формате Base64
КонецФункции
//Отпечаток - строка HEX
Функция ПолучитьСертификатПоОтпечатку(ОтпечатокСтр)
Рез = Неопределено; // Найденный сертификат (Com-объект)
CAPICOM_CURRENT_USER_STORE = 2;
//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); // Открыть хранилище сертификатов
// 1 вариант: поиск сертификата по отпечатку
//CAPICOM_CERTIFICATE_FIND_SHA1_HASH = 0;
//Certificates = oStore.Certificates.Find(CAPICOM_CERTIFICATE_FIND_SHA1_HASH, ОтпечатокСтр);
//Рез = Certificates.Item(1);
//2 вариант: обходом по коллекции и сравнение с отпечатком
Для Каждого ТекСертификат Из oStore.Certificates Цикл
ТекОтпечаток = ТекСертификат.Thumbprint; // возвращается отпечаток в шестнадцатеричном виде
Если ВРЕГ(ТекОтпечаток) = ВРЕГ(ОтпечатокСтр) Тогда Рез = ТекСертификат;
Прервать;
КонецЕсли;
КонецЦикла;
oStore.Close(); // Закрыть хранилище сертификатов и освободить объект 61
Возврат Рез;
КонецФункции
Показать
У клиента менялся сертификат, может быть в этом причина? Хотя при "ручном" подписании ошибок нет.
Тут вопрос по реализации открепленной подписи, так как она мне нужна:
Функция ПодписатьТекстОтсоединеннаяПодпись(ТекстДляПодписи,Отпечаток)
ФайлСПодписями = ПолучитьИмяВременногоФайла("txt");
МенеджерКриптографии = Новый МенеджерКриптографии("Microsoft Enhanced Cryptographic Provider v1.0","",1);
Хранилище = МенеджерКриптографии.ПолучитьХранилищеСертификатов(ТипХранилищаСертификатовКриптографии.ПерсональныеСертификаты);
Сертификат = Хранилище.НайтиПоОтпечатку(ПолучитьДвоичныеДанныеИзHexСтроки(Отпечаток));
ДД_Подпись = МенеджерКриптографии.Подписать(ТекстДляПодписи,ФайлСПодписями,Сертификат);
Возврат ПолучитьСтрокуИзДвоичныхДанных(ДД_Подпись,"windows-1251");
КонецФункции// ПодписатьТекстОтсоединеннаяПодпись()
Но получаю ошибку:
ОбщийМодуль.vvoИнтеграция.Модуль(986)}: Ошибка при вызове метода контекста (Подписать)
ДД_Подпись = МенеджерКриптографии.Подписать(ТекстДляПодписи,ФайлСПодписями,Сертификат);
по причине:
Ошибка операции с файлом.
по причине:
Каталог не обнаружен 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0id2luZG93cy0xMjUxIiA\Pg0KPNTg6esgwuXw8c\w7uM9ItHB6NEzIiDC5fDx1O7w7D0iNS4wMSIgyOTU4OnrPSJPTl9OU.... Там очень длинный текст.
Показать
Есть какой - то рабочий код? Параметры для Новый МенеджерКриптографии взял из интернета, где их посмотреть вообще?
{ОбщийМодуль.vvoИнтеграция.Модуль(986)}: Ошибка при вызове метода контекста (Подписать)
ДД_Подпись = МенеджерКриптографии.Подписать(ТекстДляПодписи,Сертификат);
по причине:
Ошибка операции с файлом.
по причине:
Каталог не обнаружен 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0id2luZG93cy0xMjUxIiA\Pg0KPNTg6esgwuXw8c\w7uM9ItHB6NEzIiDC5fDx1O7w7D0iNS4wMSIgyOTU4OnrPSJPTl9OU0NIRkRPUFBSTUFSS18yTFQtMTEwMDAwNjYwMDRfMkJFMDFiOTEyYTRlYzk1NGMzMDllZjA4ZGMzZGYxO.... далее очень длинный текст
ФайлСПодписями = ПолучитьИмяВременногоФайла("txt");
МенеджерКриптографии = Новый МенеджерКриптографии("Crypto-Pro GOST R 34.10-2012 Cryptographic Service Provider","",80);
Хранилище = МенеджерКриптографии.ПолучитьХранилищеСертификатов(ТипХранилищаСертификатовКриптографии.ПерсональныеСертификаты);
Сертификат = Хранилище.НайтиПоОтпечатку(ПолучитьДвоичныеДанныеИзHexСтроки(Отпечаток));
ДД_Подпись = МенеджерКриптографии.Подписать(ТекстДляПодписи,Сертификат);
Возврат ПолучитьСтрокуИзДвоичныхДанных(ДД_Подпись,"windows-1251");
(27) Тут ругается что в имени титула есть МАРК а в имени файла продавца внутри титула нет, либо наоборот. Я получаю перед подписью содержимое документа /content и из него беру все данные для титула. Все завелось после того как использовал объявление XML
ФайлТитула = ПолучитьИмяВременногоФайла("xml");
// выгрузка файла
ТекстЗапроса = Новый ЗаписьXML();
ТекстЗапроса.ОткрытьФайл(ФайлТитула, "windows-1251");
ТекстЗапроса.ЗаписатьОбъявлениеXML();
.............и дальше по схеме
Коллеги, всем спасибо! Все получилось! Проблема была в том, что сервер ЧЗ возвращал мне одну ошибку, а Postman совсем другую (правильную). Основные проблемы были с заполнением файла покупателя.