Форум OlegON > Программы и оборудование для автоматизации торговли > Маркировка

как работать с API Честного Знака ? : Маркировка

15.12.2024 21:32


28.02.2024 19:40
Вот свежий True API, если что:

https://storage.olegon.ru/supermag/u...2-00-29.pdf.7z
(17.32Мб)
29.02.2024 06:35
Спасибо
Документация: "Подписанные УКЭП зарегистрированного участника оборота товаров случайные данные в base64 (присоединённая электронная подпись)"
использую команду

Код:
cryptcp -sign -dn "INN" -uMy data out
(data - файл с полученным токеном (30 знаков), Out - файл ответа )

получается так:

это подписание в формате base64 ? Там такая хуча ключей, добавлять штамп времени или нет, создавать подпись CAdES-X Long Type 1 или нет и пр. Это никак не регламентируется. Приведенные примеры для 1С библиотек, но у нас этим тут не пахнет, вот и мучаюсь
Проект свой (рабочее место кассира) тащим уже лет 20, он еще на 7-х дельфях, десятки тысяч строк отлаженного кода, в регионе как-то распространен, ЕГАИС, протокол 1.2 добавили, маркировку, вскрытие делимых ресурсов (сыры, пиво, парфюмерия в розлив), грядут запретительные меры, хотел вот сделать постановку на кран изнутри, на авторизации "сел в лужу"

базовый адрес для обмена
Код:
url:='https : // markirovka . crpt . ru / api / v3 / true-api ';
потом в запроса добавляю суффиксы
Код:
 
url+'/auth/key'         // для первого запроса
url+'/auth/simpleSignIn'       //для получения токена
url+'/lk/documents/create?pg=beer'           // для отправки документа
для обмена используется развитый http-компонент с поддержкой ssl, хотя и применение curl дает совершенно тот же результат

(модераторам: совет использовать [соde] для обрамления http адресов - не сработал, или я чего-то "не догал", возраст, понимаете ли ..)

(0.08Мб)
29.02.2024 07:20
Хм... Я пишу на 1с77, в которой отродясь не было ничего для работы с ЭЦП и интернетом. Поэтому, об 1с можно забыть. Для этого я использую вставки JS или VB скриптов. Подписание делаю через CADESCOM от КриптоПро и нет там ни какой кучи параметров, у метода sign их всего три -данные, объект ЭЦП, и вид-открепленная/прикрепленная. Если хотите, могу выложить фрагмент скрипта подписания.
29.02.2024 07:34
Цитата:
Ilya_Nsk вот и мучаюсь
посмотри ещё тут, может поможет разобраться в причинах

Цитата:
MWWRuza Если хотите, могу выложить фрагмент скрипта подписания
выкладывай конечно, раз под рукой
29.02.2024 07:42
Чуть позже, я ещё в постели, я.... ч**у, я не по н. с. ЧП живу
29.02.2024 09:00
В общем, ладно, поспать все равно не получилось, может вечером получится не как обычно, в 2-3 часа ночи лечь, а хотя-бы часов в 12..
По порялку - я буду вставлять куски кода, "как они у меня есть", где-то на 1с77, гдето вставки скриптов... Разберетесь, 1с очень похожа на Дельфи, или чуть меньше на VB...Только возможности сильно "урезаны", даже по сравнению с "младшей сестрой - 1с8.Х"... Многих объектов не хватает, но, есть несколько "специфических", которым все равно в других ЯП есть аналоги...
Ладно, это все лирика, поехали...
Есть у меня такая обработка, проверка связи и получение токена.
Ну, тут по смыслу иожно понять, что она должна делать.
Что-бы было понятнее, дальше все буду делать через отладчик.
Жму кнопку "Выполнить проверку".
Попадаю сюда:

Процедура Сформировать()
Если Вопрос("Выполнить проверку связи с сервером ЦРПТ и попытку авторизации? Токен доступа будет получен новый. Продолжить?", 4, 10) = 6 Тогда
Если ФС.СуществуетФайл(КаталогИБ() + КодТекПольз + "_tokenCRPT.txt") = 1 Тогда
ФС.УдалитьФайл(КаталогИБ() + КодТекПольз + "_tokenCRPT.txt");
КонецЕсли;
СтрОшибка = "";
ЮрЛицо = глЗначениеПоУмолчанию("ОсновноеПодразделение");
КлючСессии = ПолучитьТокенЦРПТ(глПользователь.ЭЦП, ПрефиксВерсии, СтрОшибка);
Если ПустоеЗначение(КлючСессии) = 1 Тогда
Сообщить(СтрОшибка);
Возврат;
Иначе
Сообщить("Соединение с сервером ЦРПТ успешно установлено, ключ сессии получен!");
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Что он там ищет и удаляет, не принципиально - это храненин готового токена в течении его срока жизни, сейчас, это не принципиально.
Дальше попадаю в функцию ПолучитьТокенЦРПТ(ВыбЭЦП, ПрефиксВерсии = "", СтрОшибка = "") где параметр "ВыбЭЦП" - это отпечаток ЭЦП, хранящийся в одном из справочников. Остальные параметры тут не принципиальны.
Следующим шагом попадаю в функцию /
/ Возвращает пары идентификатор-данные для запроса токена
ВернутьПаруЗапрТокена(УРЛ, ПрефиксВерсии = "", ЭДО = 0) - тут параметры тоже не принципиальны. не важно как, с помощью них получаю адрес запроса:
Url = "https://markirovka.crpt.ru/api/v3/true-api/auth/key"
Формирую заголовки запроса:
сзЗаголовки.ДобавитьЗначение("no-cache", "cache-control");
сзЗаголовки.ДобавитьЗначение("application/json", "Accept");
И далее сам запрос:
Ответ = глКарлик_ВыполнитьЗапрос("GET", Url,, сзЗаголовки, СтрОшибка);

чем Вы его тут будете делать - совершенно не принципиально, MS_WinHTTP, MS_XMLHTTP, cUrl, или чем-то своим - это как удобнее и привычнее. Раньше я делал через MS запросы, сейчас использую внешнюю компоненту для 1с77 - curl1c на базе curl, по сути - это курл, завернутый в оболочку ВК, и работа с ней ничем не отнличается от MS запросов, никаких "командных строк" и т.п., все просто и удобно, зато нет никаких гимороев с httpS как в случае с MS запросами.

Далее, куча кода по анализу ответа и разбор ошибок, если есть, но, это не интересно, опустим...
Короче, получили ответ - строку JSON, разобрали ее(тут тоже свои нюансы, 7.7 не знает ничего о JSON, приходится "извращаться"), имеем две строки:

uuid = "04fbe037-1a2d-4958-a1aa-50bae5bb6240"
code = "MPXHFICBNZHDRNAGQONJBYYCCOWMRB"

УУИД - это идентификатор токена, КОДЕ - строка "произвольных данных" для подписи.
НО!!! Вот тут надо четко понимать, что это не "произвольные данные" "от балды", как для формирования токена для CDN площадок, а данные сформированные сервером и "привязанные" к УУИД.

Далее идет функция:

Сигнатура = ПодписатьТекст(code,СокрЛП(ВыбЭЦП.Отпечаток),1); - три параметра, сама строка, отпечаток, и "1", это то, что она открепленная.

Сама функция:

// sThumbprint - отпечаток сертификата, используемого для подписи; строка, представляющая отпечаток в шестандцатеричном виде
Функция ПодписатьТекст(ТекстДляПодписи, sThumbprint,БезBOM=1, Откр = 0) Экспорт

infile = КаталогВременныхФайлов()+"infile.txt";
Если ФС.СуществуетФайл(infile) = 1 Тогда
Попытка
ФС.УдалитьФайл(infile);
Исключение
infile = КаталогВременныхФайлов() + "infile" + ГенераторGIUD() + ".txt";
КонецПопытки;
КонецЕсли;
outfile = КаталогВременныхФайлов()+"outfile.txt";
Если ФС.СуществуетФайл(outfile) = 1 Тогда
Попытка
ФС.УдалитьФайл(outfile);
Исключение
outfile = КаталогВременныхФайлов() + "outfile" + ГенераторGIUD() + ".txt";
КонецПопытки;
КонецЕсли;
Если Прав(ТекстДляПодписи,4) = ".xml" Тогда
infile = ТекстДляПодписи;
ПодписатьФайл(infile,ПолучитьСертификатПоОтпечатку(sThumbprint),outfile, Откр);
Если ФС.СуществуетФайл(outfile) = 1 Тогда
Возврат outfile;
Иначе
Возврат "";
КонецЕсли;
Иначе
Если БезBOM = 1 Тогда
ВремТекст = СоздатьОбъект("Текст");
ВремТекст.ДобавитьСтроку(ТекстДляПодписи);
ВремТекст.Записать(infile);
Иначе
АдоДБСтрим = СоздатьОбъект("ADODB.Stream");
АдоДБСтрим.Mode = 3;
АдоДБСтрим.Type = 2;//текст
АдоДБСтрим.charset="utf-8";
АдоДБСтрим.Open();
АдоДБСтрим.WriteText(ТекстДляПодписи);
АдоДБСтрим.Position=0;
АдоДБСтрим.SaveToFile(infile,2);
АдоДБСтрим.Close();
КонецЕсли;
ПодписатьФайл(infile,ПолучитьСертификатПоОтпечатку(sThumbprint),outfile, Откр);
Если ФС.СуществуетФайл(outfile) = 1 Тогда
Возврат outfile;
Иначе
Возврат "";
КонецЕсли;
КонецЕсли;

КонецФункции

Напрямую подписать строку у меня не получилось, поэтому через поток формирую временный файл, и его подписываю функцией ПодписатьФайл(infile,ПолучитьСертификатПоОтпечатку(sThumbprint),outfile, Откр);
Где:
infile = "C:\Users\MWW\AppData\Local\Temp\infile.txt"
А outfile получим после подписания.

Сертификат по отпечатку получаю так:

//https://www.cryptopro.ru/forum2/default.aspx?g=posts&m=95369
//https://www.cryptopro.ru/forum2/default.aspx?g=posts&t=10684
//Отпечаток - строка HEX
Функция ПолучитьСертификатПоОтпечатку(ОтпечатокСтр) Экспорт
Рез = ""; // Найденный сертификат (Com-объект)
CAPICOM_CURRENT_USER_STORE = 2; // 2 - Искать сертификат в ветке "Личное" хранилища.
CAPICOM_MY_STORE = "My"; // Указываем, что ветку "Личное" берем из хранилища текущего пользователя
CAPICOM_STORE_OPEN_READ_ONLY = 0; // Открыть хранилище только на чтение
oStore = СоздатьОбъект("CAPICOM.Store"); // Объект описывает хранилище сертификатов
oStore.Open(CAPICOM_CURRENT_USER_STORE, CAPICOM_MY_STORE, CAPICOM_STORE_OPEN_READ_ONLY); // Открыть хранилище сертификатов
Certs = oStore.Certificates;
Для СчСер = 1 По Certs.Count Цикл
ТекСертификат = Certs.Item (СчСер);
ТекОтпечаток = ТекСертификат.Thumbprint; // возвращается отпечаток в шестнадцатеричном виде
Если ВРЕГ(ТекОтпечаток) = ВРЕГ(ОтпечатокСтр) Тогда
Рез = ТекСертификат;
Прервать;
КонецЕсли;
КонецЦикла;
oStore.Close(); // Закрыть хранилище сертификатов и освободить объект
Возврат Рез;
КонецФункции

Пока хватит, а то месага слишком длинная будет, продолжение следует
29.02.2024 09:04
Вот сама функция подписания файла, тут немного "намешано", и капиком был раньше, и кадеском сейчас, но, не принципиально, если что-то не понятно поясню позже:

// на основе
// Пар = 0 подписываем CAPICOM подпись прикрепленная, Пар = 1 подписываем CAdESCOM, подпись открепленная
Функция ПодписатьФайл(ИмяФайла, ВыбСертификат, ИмяВыхФайла, Пар = 0) Экспорт
Если ВыбСертификат = "" Тогда
Сообщить("В хранилище сертификатов отсутствует выбранный сертификат!");
Возврат 0;
КонецЕсли;
Попытка
JS=СоздатьОбъект("MSScriptControl.ScriptControl");
JS.Language="jscript";
JS.Timeout=-1;
Исключение
ТекстОшибки=ОписаниеОшибки();
Сообщить("Не удалось создать объект MSScriptControl.ScriptControl","!");
Сообщить("Описание ошибки: "+ТекстОшибки,"!");
Возврат 0;
КонецПопытки;
Попытка
Если Пар = 0 Тогда
СтрКода="function SignFile(FileName,Cert,OutFileName)
|{
| InStream=new ActiveXObject(""ADODB.Stream"");
| InStream.Type=1; // binary data
| InStream.Mode=3; // read/write
| InStream.Open();
| InStream.LoadFromFile(FileName);
| InData=InStream.Read(-1);
|
| Signer=new ActiveXObject(""CAPICOM.Signer"");
| Signer.Certificate=Cert;
| Signer.Options=2; // CAPICOM_CERTIFICATE_INCLUDE_END_ENTITY_ONLY
| SignedData=new ActiveXObject(""CAPICOM.SignedData"");
| SignedData.Content=InData;
| OutSignedData=SignedData.Sign(Signer,0,0);
|
| OutStream=new ActiveXObject(""ADODB.Stream"");
| OutStream.CharSet=""utf-8"";
| OutStream.Type=2; // text data
| OutStream.Mode=3; // read/write
| OutStream.Open();
| OutStream.WriteText(OutSignedData);
| OutStream.SaveToFile(OutFileName,2);
| OutStream.Close();
|
| return(0);
|}
|";
Иначе
// Sign(Signer,0,1) - проба открепленной ЭЦП
СтрКода="function SignFile(FileName,Cert,OutFileName)
|{
| InStream=new ActiveXObject(""ADODB.Stream"");
| InStream.Type=1; // binary data
| InStream.Mode=3; // read/write
| InStream.Open();
| InStream.LoadFromFile(FileName);
| InData=InStream.Read(-1);
|
| Signer=new ActiveXObject(""CAdESCOM.CpSigner"");
| Signer.Certificate=Cert;
| Signer.Options=2; // CAPICOM_CERTIFICATE_INCLUDE_END_ENTITY_ONLY
| SignedData=new ActiveXObject(""CAdESCOM.CadesSignedData"");
|
|
| SignedData.Content=InData;
| OutSignedData=SignedData.SignCades(Signer,1,1,0); // параметр 3 - открепленная/прикрепленная(1 - открепленная), пар 4 - кодировка(0 - Base64)
// | OutSignedData=SignedData.SignCades(Signer,1,1,0); // параметр 3 - открепленная/прикрепленная(1 - открепленная), пар 4 - кодировка(0 - Base64)
| OutStream=new ActiveXObject(""ADODB.Stream"");
| OutStream.Type=2; // text data
// | OutStream.CharSet=""US-ASCII"";
| OutStream.CharSet=""utf-8"";
| OutStream.Mode=3; // read/write
| OutStream.Open();
| OutStream.WriteText(OutSignedData);
| OutStream.SaveToFile(OutFileName,2);
| OutStream.Close();
|
| return(0);
|}
|";
КонецЕсли;
JS.AddCode(СтрКода);
Рез = JS.Modules("Global").CodeObject.SignFile(ИмяФайла,ВыбСертификат,ИмяВыхФайла);
Исключение
ТекстОшибки=ОписаниеОшибки();
Сообщить("Произошла ошибка при подписи файла!","!");
Сообщить("Описание ошибки: "+ТекстОшибки,"!");
Возврат 0;
КонецПопытки;
Возврат 1;
КонецФункции
29.02.2024 09:13
В итоге, получаю файл:
outfile = "C:\Users\MWW\AppData\Local\Temp\outfile.txt"

Дальше, убираю переносы строк:

//******************************************************************************
// УбратьПереносыСтрок(ПутьКСертификату)
//
// Параметры:
// ПутьКСертификату - путь к файлу или текст сертификата КЭП
//
// Возвращаемое значение:
// public_cert,signature
//
// Описание:
// убирает переносы строк и возвращает содержимое в виде строки
//
Функция УбратьПереносыСтрок(ПутьКСертификату, Кодировка = 0) Экспорт

ВозврPublic_cert = "";
Если (Прав(ПутьКСертификату,4) = ".cer") ИЛИ (Прав(ПутьКСертификату,4) = ".txt") ИЛИ (Прав(ПутьКСертификату,4) = ".sig") Тогда
Если ФС.СуществуетФайл(ПутьКСертификату) = 0 Тогда
Сообщить("Файл сертификата "+СокрЛП(ПутьКСертификату)+" не найден");
Возврат "";
КонецЕсли;
АдоДБСтрим = СоздатьОбъект("ADODB.Stream");
АдоДБСтрим.Mode = 3;
АдоДБСтрим.Type = 2;//текст
Если Кодировка = 0 Тогда
АдоДБСтрим.charset="utf-8";
Иначе
АдоДБСтрим.charset="windows-1251";
КонецЕсли;
АдоДБСтрим.Open();
АдоДБСтрим.LoadFromFile(ПутьКСертификату);
АдоДБСтрим.Position = 0;
ТекстСертификата = АдоДБСтрим.ReadText(-1);
АдоДБСтрим.Close();
Для СчСтрок = 1 По СтрКоличествоСтрок(ТекстСертификата) Цикл
ТекСтрока = СтрПолучитьСтроку(ТекстСертификата,СчСтрок);
Если Лев(ТекСтрока,4) = "----" Тогда
Продолжить;
КонецЕсли;
ТекСтрока = СтрЗаменить(ТекСтрока,РазделительСтрок,"");
ВозврPublic_cert = ВозврPublic_cert + ТекСтрока;
КонецЦикла;
Иначе
Для СчСтрок = 1 По СтрКоличествоСтрок(ПутьКСертификату) Цикл
ТекСтрока = СтрПолучитьСтроку(ПутьКСертификату,СчСтрок);
Если Лев(ТекСтрока,4) = "----" Тогда
Продолжить;
КонецЕсли;
ТекСтрока = СтрЗаменить(ТекСтрока,РазделительСтрок,"");
ВозврPublic_cert = ВозврPublic_cert + ТекСтрока;
КонецЦикла;
КонецЕсли;

Возврат ВозврPublic_cert;

КонецФункции // УбратьПереносыСтрок()

Создаю список значений(ну, это 1Совский объект, не принципиально, нужно для перевода всей этой "лабуды" в строку JSON):

сзJSONЗапрос.Установить("uuid",uuid);
сзJSONЗапрос.Установить("data",Сигнатура);

где УУИД - как есть ИД запроса с сервера, а Сигнатура - строка которая получилась после удаления переносов на предыдущем шаге.
29.02.2024 09:23
Тело запроса получается такое: ТелоЗапр = "{"uuid": "0deb04b6-0e2a-415f-aa61-4b1f3884ec23", "data": "MIINlQYJKoZIhvcNAQcCoIINhjCCDYICAQExDjAMBggqhQMHAQECAgUAMC0GCSqGSIb3DQEHAaAgBB5QRExPWlRFS0tGSVFMWFBWREFHVENJVFdESlNRQliggglzMIIJbzCCCRygAwIBAgIRB9Sj7wDEr7uISQijnkdGcBQwCgYIKoUDBwEBAwIwggE0MRUwEwYF"

Только естеставенно, гораздо длиннее, просто в отладчике строка обрезана, а в файл сохранять нужно дописывать, раньше это было, сейчас убрал..
Отправляю этот запрос сюда:
Url = "https://markirovka.crpt.ru/api/v3/true-api/auth/simpleSignIn"
И в ответ прилетает токен, который я потом сохраняю в файл через список 1С, что-бы вместе с ним сохранить дату/время для дальнейшего его использования и проверки что он "живой"(это то, что я умышленно опустил на первом шаге этой обработки).
Собственно, и все...
Потом, этот токен работает везде, где требуется авторизация беарер...
29.02.2024 09:24
Рабочий vbscript для подписания ниже. Вообще, интеграцию лучше в классы заворачивать. Я понимаю, что в 1С их нет.

Код:
  CURRENT_USER_STORE = 2
  MY_STORE = "My"
  STORE_OPEN_READ_ONLY = 0
  CAPICOM_CERTIFICATE_FIND_SHA1_HASH = 0
  CertSHA1Hash = "....."  'отпечаток из свойств сертификата

  CAPICOM_ENDCODE_BASE64 = 0    'преобразование выходного файла в base64
  
  Set objArgs = WScript.Arguments  'получаем аргументы
  InFileName  = objArgs(0)      'имя подписываемого файла 
  OutFileName = objArgs(1)      'имя подписанного файла
  bDetached =   objArgs(2)      '0 - совмещенная, 1 - отделенная подпись

  'поиск сертификата в хранилище по отпечатку
  Set Store = CreateObject ("CAPICOM.Store")   
  Store.Open CURRENT_USER_STORE, MY_STORE, STORE_OPEN_READ_ONLY
  Set Certs = Store.Certificates.Find(CAPICOM_CERTIFICATE_FIND_SHA1_HASH, CStr (CertSHA1Hash))
  if Certs.Count > 0 Then
     Set GetSigningCert = Certs.Item (1) 
  else
     Set GetSigningCert = Nothing
  end if
  Store = Unassgned 

  if not GetSigningCert is nothing Then   'если сертификат найден, то подписываем
     set InStream = CreateObject("AdoDB.Stream")  'читаем исходный файл
     InStream.Type = 1
     InStream.Mode = 3
     InStream.Open()
     InStream.LoadFromFile(InFileName)
     InData = InStream.Read(-1)
  
     Set Signer = CreateObject ("CAPICOM.Signer")   'подписываем
     Signer.Certificate = GetSigningCert
     Signer.Options = 2
     Set SignedData = CreateObject ("CAPICOM.SignedData")
     SignedData.Content = InData
     OutSignedData = SignedData.Sign(Signer,bDetached,CAPICOM_ENDCODE_BASE64)

     set OutStream = CreateObject("AdoDB.Stream")  'сохраняем подписанный файл или подпись
     OutStream.CharSet = "US-ASCII"
     OutStream.Type = 2
     OutStream.Mode = 3
     OutStream.Open()
     OutStream.WriteText(OutSignedData)
     OutStream.SaveToFile OutFileName,2 
     OutStream.Close()
     InStream.Close()

     SignedData = Unassgned
     Signer = Unassigned
     FileIn = Unassigned
     FileOut= Unassigned
  end if
Часовой пояс GMT +3, время: 21:32.

Форум на базе vBulletin®
Copyright © Jelsoft Enterprises Ltd.
В случае заимствования информации гипертекстовая индексируемая ссылка на Форум обязательна.