Продолжаю описание обмена с Честным знаком. В предыдущей статье был описан алгоритм получения списка сертификатов из личного хранилища.
Этап 2. Установка подключения состоит из основных блоков:
- Отправка запроса на подключение. В ответ мы получаем от сервера честного знака пару uuid-data.
- Подпись данных и отправка их обратно на сервер. В ответ мы получим токен сессии, с которым в дальнейшем будем отправлять документы.
Ниже приведены основные процедуры и функции получения токена соединения с сервером Честного знака.
В целях уменьшения приведенного кода в этой статье и последующих, процедуры и функции работы с JSON приведены в отдельной статье JSON и 1С7.7.
Функция ПолучитьТокенСесии(АдресСервера, ОтпечатокСертификата) Перем ДанныеДляАвторизации, uuid, code; Перем Сигнатура; Перем URL, сзJSONЗапрос; Перем ОтветСервера, ОтветСервераСЗ; Перем ТекстСообщения; ДанныеДляАвторизации = ОтправитьЗапросНаАвторизацию(АдресСервера); Если ДанныеДляАвторизации = 0 Тогда Возврат 0; КонецЕсли; uuid = ДанныеДляАвторизации.Получить("uuid"); code = ДанныеДляАвторизации.Получить("data"); Сигнатура = ПодписатьТекст(code, ОтпечатокСертификата, 1); Если ПустоеЗначение(Сигнатура) = 1 Тогда Возврат 0; КонецЕсли; Сигнатура = ПолучитьПубличнуюЧастьСертификата(Сигнатура); сзJSONЗапрос = СоздатьОбъект("СписокЗначений"); сзJSONЗапрос.Установить("uuid", uuid); сзJSONЗапрос.Установить("data", Сигнатура); URL = АдресСервера; ОтветСервера = HTPP_ВыполнитьЗапрос("POST", URL + "/auth/simpleSignIn", сзJSONЗапрос,,,); Если ПустоеЗначение(ОтветСервера) = 0 Тогда ОтветСервераСЗ = JSONВСписок(ОтветСервера); КонецЕсли; Если ПустоеЗначение(ОтветСервераСЗ) = 1 Тогда Возврат 0; КонецЕсли; Результат = 0; Если ПустоеЗначение(ОтветСервераСЗ.Получить("token")) = 0 Тогда Результат = ОтветСервераСЗ.Получить("token"); Иначе ТекстСообщения = ОтветСервераСЗ.Получить("error_message"); Если ПустоеЗначение(ТекстСообщения) = 0 Тогда Сообщить(ТекстСообщения, "!!"); КонецЕсли; КонецЕсли; Возврат Результат; КонецФункции Функция ОтправитьЗапросНаАвторизацию(АдресСервера) Перем Соединение, URL, RequestTimeout; Перем Результат, ОтветСервера; Перем ТекстСообщения; URL = АдресСервера + "/auth/key"; Соединение = HTPP_ПолучитьОбъект("GET", URL); Если Соединение = 0 Тогда Возврат 0; КонецЕсли; Соединение.Send(); RequestTimeout = 40; Попытка Результат = Соединение.WaitForResponse(RequestTimeout); Исключение Результат = 0; ТекстСообщения = "Превышено время ожидания ответа сервера авторизации! " + ОписаниеОшибки(); Сообщить(ТекстСообщения, "!!"); КонецПопытки; Если Результат = -1 Тогда ОтветСервера = СокрЛП(Соединение.ResponseText()); Результат = JSONВСписок(ОтветСервера); Если ТипЗначенияСтр(Результат) = "СписокЗначений" Тогда Если (ПустоеЗначение(Результат.Получить("uuid")) = 1) или (ПустоеЗначение(Результат.Получить("data")) = 1) Тогда Результат = 0; КонецЕсли; Иначе Результат = 0; КонецЕсли; Если Результат = 0 Тогда ТекстСообщения = "От серевера получен некорректный ответ!"; Сообщить(ТекстСообщения, "!!"); КонецЕсли; Иначе ТекстСообщения = "Нет ответа от сервера авторизации!"; Сообщить(ТекстСообщения, "!!"); КонецЕсли; Возврат Результат; КонецФункции //**************************** // Создает и возвращает объект WinHttp // Параметры: // Нет // Возвращаемое значение: // Объект WinHttp или 0 Функция HTPP_ПолучитьОбъект(ТипЗапроса, URL) Перем ТекстСообщения; Попытка Результат = СоздатьОбъект("WinHttp.WinHttpRequest.5.1"); Результат.Option(2, "utf-8"); Результат.SetTimeouts(0, 0, 0, 0); Результат.Open(ТипЗапроса, URL, 1); Результат.SetRequestHeader("Content-Type", "application/json"); Результат.SetRequestHeader("Accept-Language", "ru"); Результат.setRequestHeader("Content-Language", "ru"); Исключение ТекстСообщения = "Не удалось создать объект <WinHttp>!"; Сообщить(ТекстСообщения, "!!"); Результат = 0; КонецПопытки; Возврат Результат; КонецФункции Функция ПодписатьТекст(ТекстДляПодписи, ОтпечатокСертификата, БезBOM = 1, ВернутьКакСтроку = 0) Перем ФайлИсходный, ФайлПодписанный; Перем ВремТекст; ФайлИсходный = КаталогВременныхФайлов() + "infile.txt"; Если ФС.СуществуетФайл(ФайлИсходный) = 1 Тогда Попытка ФС.УдалитьФайл(ФайлИсходный); Исключение ФайлИсходный = КаталогВременныхФайлов() + "infile" + СформироватьGIUD() + ".txt"; КонецПопытки; КонецЕсли; ФайлПодписанный = КаталогВременныхФайлов() + "outfile.txt"; Если ФС.СуществуетФайл(ФайлПодписанный) = 1 Тогда Попытка ФС.УдалитьФайл(ФайлПодписанный); Исключение ФайлПодписанный = КаталогВременныхФайлов() + "ФайлПодписанный" + СформироватьGIUD() + ".txt"; КонецПопытки; КонецЕсли; Если Прав(ТекстДляПодписи, 4) = ".xml" Тогда ФайлИсходный = ТекстДляПодписи; ВыполнитьПодписаниеФайла(ФайлИсходный, ФайлПодписанный, Подпись_ПолучитьСертификатПоОтпечатку(Отпечаток)); Если ФС.СуществуетФайл(ФайлПодписанный) = 1 Тогда Возврат ФайлПодписанный; Иначе Возврат ""; КонецЕсли; Иначе Если БезBOM = 1 Тогда ВремТекст = СоздатьОбъект("Текст"); ВремТекст.ДобавитьСтроку(ТекстДляПодписи); ВремТекст.Записать(ФайлИсходный); Иначе АдоДБСтрим = СоздатьОбъект("ADODB.Stream"); АдоДБСтрим.Mode = 3; АдоДБСтрим.Type = 2;//2текст АдоДБСтрим.charset="utf-8"; АдоДБСтрим.Open(); АдоДБСтрим.WriteText(ТекстДляПодписи); АдоДБСтрим.Position=0; АдоДБСтрим.SaveToFile(ФайлИсходный, 2); АдоДБСтрим.Close(); КонецЕсли; ВыполнитьПодписаниеФайла(ФайлИсходный, ФайлПодписанный, Подпись_ПолучитьСертификатПоОтпечатку(Отпечаток)); Если ФС.СуществуетФайл(ФайлПодписанный) = 1 Тогда Если ВернутьКакСтроку = 1 Тогда Возврат РаботаСФайлами_ПолучитьТекстФайлаКакСтроку(ФайлПодписанный); Иначе Возврат ФайлПодписанный; КонецЕсли; Иначе Возврат ""; КонецЕсли; КонецЕсли; КонецФункции //**************************** // Подписывает файл с помощью скрипта // Параметры: // ИмяФайлаВходящего - имя файла, который требуется подписать // ИмяФайлаРезультат - имя файла-результат (подписанный исходный файл) // Сертификат - сертификат для подписи // Возвращаемое значение: // 1 - успешно, 0 - ошибка Функция ВыполнитьПодписаниеФайла(ИмяФайлаВходящего, ИмяФайлаРезультат, Сертификат) Перем JS, ТекстСкрипта; Перем ТекстСообщения; Попытка JS=СоздатьОбъект("MSScriptControl.ScriptControl"); JS.Language="jscript"; JS.Timeout=-1; Исключение ТекстСообщения = "Не удалось создать объект MSScriptControl.ScriptControl! " + ОписаниеОшибки(); Сообщить(ТекстСообщения, "!!"); Возврат 0; КонецПопытки; Попытка ТекстСкрипта="function SignFile(FileName, OutFileName, Cert) |{ | 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); |} |"; JS.AddCode(ТекстСкрипта); Рез = JS.Modules("Global").CodeObject.SignFile(ИмяФайлаВходящего, ИмяФайлаРезультат, Сертификат); Исключение ТекстСообщения = "Произошла ошибка при подписи файла! " + ОписаниеОшибки(); Сообщить(ТекстСообщения, "!!"); Возврат 0; КонецПопытки; Возврат 1; КонецФункции //**************************** // Функция ищет в хранилище сертификат по отпечатку // Параметры: // Отпечаток - строка, отпечаток сертификата // Возвращаемое значение: // Сертификат (COM-объект) Функция Подпись_ПолучитьСертификатПоОтпечатку(Отпечаток) Перем oStore, текСертификат, текОтпечаток; oStore = Подпись_ПолучитьОбъектХранилищеСертификатов(); Если oStore = 0 Тогда Возврат ""; КонецЕсли; Результат = ""; // Найденный сертификат (Com-объект) Certs = oStore.Certificates; Для СчСер = 1 По Certs.Count Цикл текСертификат = Certs.Item (СчСер); текОтпечаток = текСертификат.Thumbprint; // возвращается отпечаток в шестнадцатеричном виде Если ВРЕГ(текОтпечаток) = ВРЕГ(Отпечаток) Тогда Результат = текСертификат; Прервать; КонецЕсли; КонецЦикла; oStore.Close(); // Закрыть хранилище сертификатов и освободить объект Возврат Результат; КонецФункции //**************************** // Выполнить HTTP запрос // Параметры: // ТипЗапроса="POST",Url,сзJSONЗапрос,КлючСессии="",сзЗаголовки="" // Возвращаемое значение: // Ответ сервера Функция HTPP_ВыполнитьЗапрос(ТипЗапроса="POST", URL, сзJSONЗапрос, КлючСессии="", сзЗаголовки="", НеОбр429 = 0) Перем WinHttp, RequestTimeout, РезультатHTTP; Перем ИмяЗаголовка, ЗначениеЗаголовка, Счетчик; Перем Статус, СтатусТекст, Ответ, сзОтвет; Перем ТекстСообщения; WinHttp = HTPP_ПолучитьОбъект(ТипЗапроса, URL); Если WinHttp = 0 Тогда Возврат 0; КонецЕсли; Если ПустоеЗначение(КлючСессии) = 0 Тогда WinHttp.SetRequestHeader ("Authorization", "Bearer " + КлючСессии); КонецЕсли; Если ТипЗначенияСтр(сзJSONЗапрос) = "СписокЗначений" Тогда ЗапросJSON = ЗначениеВJSON(сзJSONЗапрос); ИначеЕсли ТипЗначенияСтр(сзJSONЗапрос) = "Строка" Тогда ЗапросJSON = СокрЛП(сзJSONЗапрос); Иначе ЗапросJSON = ""; КонецЕсли; Если ПустоеЗначение(сзЗаголовки) = 0 Тогда Для Счетчик = 1 По сзЗаголовки.РазмерСписка() Цикл ИмяЗаголовка = ""; ЗначениеЗаголовка = сзЗаголовки.ПолучитьЗначение(Счетчик, ИмяЗаголовка); Если (ПустоеЗначение(ИмяЗаголовка) = 0) И (ПустоеЗначение(ЗначениеЗаголовка) = 0) Тогда WinHttp.SetRequestHeader (ИмяЗаголовка, ЗначениеЗаголовка); КонецЕсли; КонецЦикла; КонецЕсли; WinHttp.Send(ЗапросJSON); RequestTimeout = 40; Попытка РезультатHTTP = WinHttp.WaitForResponse(RequestTimeout); Исключение РезультатHTTP = 0; КонецПопытки; Если РезультатHTTP = -1 Тогда Статус = WinHttp.status(); СтатусТекст = WinHttp.statusText(); Ответ = HTPP_ПолучитьОтветСервера(WinHttp); Если Статус <> 200 Тогда Если Статус = 307 Тогда//перенаправление URL = WinHttp.GetResponseHeader("Location"); Если ПустоеЗначение(Url) = 0 Тогда Возврат HTPP_ВыполнитьЗапрос(ТипЗапроса, URL, сзJSONЗапрос, КлючСессии, сзЗаголовки); КонецЕсли; КонецЕсли; Если (Статус = 429) И (НеОбр429 = 0) Тогда //слишком много запросов ВыдержатьПаузу(5); Возврат HTPP_ВыполнитьЗапрос(ТипЗапроса, Url, сзJSONЗапрос, КлючСессии, сзЗаголовки); КонецЕсли; Если ПустоеЗначение(Ответ) = 0 Тогда Возврат Ответ; Иначе Возврат 0; КонецЕсли; КонецЕсли; Иначе ТекстСообщения = "Не удалось выполнить HTTP-запрос! " + ОписаниеОшибки(); Сообщить(ТекстСообщения, "!!"); Ответ = 0; КонецЕсли; Возврат Ответ; КонецФункции //**************************** // Фозвращает ответ сервера после выполнения HTTP запроса // Параметры: // WinHttp - объект "WinHttp.WinHttpRequest.5.1" // Возвращаемое значение: // ОтветСервера - строка Функция HTPP_ПолучитьОтветСервера(WinHttp) Перем Stream, Скрипт; Перем ИмяВременногоФайла, ИмяВременногоФайлаПерекодированного; Перем СтримВход, СтримВыход, Байт, РазмерДанных; Перем тмпТекст; Перем ТекстСообщения; ИмяВременногоФайла = КаталогВременныхФайлов() + СформироватьGIUD() + ".txt"; Результат = ""; Попытка // Получаем ответ от сервера с помощью // java-скрипта, чтобы не потерять символы utf-8 Stream = СоздатьОбъект("ADODB.Stream"); Stream.Mode = 3; Stream.Type = 1; Stream.Open(); Скрипт = СоздатьОбъект("MSScriptControl.ScriptControl"); Скрипт.Language = "javascript"; Скрипт.AddObject("WinHttp", WinHttp); Скрипт.AddObject("Stream", Stream); Скрипт.Eval("Stream.Write(WinHttp.ResponseBody)"); Stream.SaveToFile(ИмяВременногоФайла, 2); Stream.Close(); ИмяВременногоФайлаПерекодированного = РаботаСФайлами_Перекодировка(ИмяВременногоФайла, "utf-8", "windows-1251"); Если ПустоеЗначение(ИмяВременногоФайлаПерекодированного) = 1 Тогда ТекстСообщения = "Не удалось перекодировать ответ сервера!"; Сообщить(ТекстСообщения, "!!"); Результат = ""; Иначе Результат = РаботаСФайлами_ПолучитьТекстФайлаКакСтроку(ИмяВременногоФайлаПерекодированного); КонецЕсли; Исключение ТекстСообщения = "Не удалось получить ответ от сервера!"; Сообщить(ТекстСообщения, "!!"); Результат = ""; КонецПопытки; Попытка ФС.УдалитьФайл(ИмяВременногоФайла); ФС.УдалитьФайл(ИмяВременногоФайлаПерекодированного); Исключение // КонецПопытки; Возврат Результат; КонецФункции Функция РаботаСФайлами_Перекодировка(ИмяФайла, КодировкаВход, КодировкаВыход) Результат = ИмяФайла + "_k"; Попытка Байт = 255; СтримВход = СоздатьОбъект("ADODB.Stream"); СтримВход.Type = 2; СтримВход.charset = КодировкаВход; СтримВход.Open(); СтримВход.LoadFromFile(ИмяФайла); СтримВыход = СоздатьОбъект("ADODB.Stream"); СтримВыход.Type = 2; СтримВыход.charset = КодировкаВыход; СтримВыход.LineSeparator = -1; СтримВыход.Open(); РазмерДанных = СтримВход.size; Пока СтримВход.EOS = 0 Цикл СтримВыход.WriteText(СтримВход.ReadText(Байт), ?(Байт=-2,1,0)); КонецЦикла; СтримВыход.SaveToFile(Результат, 2); СтримВход.Close(); СтримВыход.Close(); Исключение ТекстСообщения = "Не удалось получить изменить кодировку!"; Сообщить(ТекстСообщения, "!!"); Результат = ""; КонецПопытки; Возврат Результат; КонецФункции Функция РаботаСФайлами_ПолучитьТекстФайлаКакСтроку(ИмяФайла, БезБОМ = 0) Перем ВсегоСтрок, Счетчик; Результат = ""; Попытка тмпТекст = СоздатьОбъект("Текст"); тмпТекст.Открыть(ИмяФайла); ВсегоСтрок = тмпТекст.КоличествоСтрок(); Для Счетчик = 1 по ВсегоСтрок Цикл текСтрока = тмпТекст.ПолучитьСтроку(Счетчик); Если (Счетчик = 1) И (БезБОМ = 1) Тогда текСтрока = Сред(текСтрока, 4); КонецЕсли; Результат = Результат + текСтрока; КонецЦикла; Исключение //; КонецПопытки; Возврат Результат; КонецФункции Процедура ВыдержатьПаузу(Секунд = 1) Перем Система; Система = СоздатьОбъект("Система"); Система.Уснуть(Секунд*1000); КонецПроцедуры Функция СформироватьGIUD() ОбъектGUID = СоздатьОбъект("GUID"); ОбъектGUID.Новый(); Возврат ОбъектGUID.ВСтроку(); КонецФункции
На этом подключение к Честному знаку завершено. Мы получили токен сессии для дальнейшего использования при передаче документа.
Токен действителен в течении 10 часов, поэтому дополнительно можно дописать его хранение в базе данных или временном файле для повторного использования. Только обязательно необходимо вместе с токеном запоминать дату и время его выдачи и проверять актуальность токена перед его использованием.
Ну в следующей статье я опишу пример отправки документа в формате JSON в Честный знак.
Если вы заметили ошибку, неточность или у вас появились вопросы, пишите в комментариях, я обязательно вам отвечу и приму все ваши замечания.
Добрый день, не могу нигде найти функцию
ПолучитьПубличнуюЧастьСертификата(Сигнатура).