Продолжаю описание обмена с Честным знаком. В предыдущей статье был описан алгоритм получения списка сертификатов из личного хранилища.
Этап 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 в Честный знак.
Если вы заметили ошибку, неточность или у вас появились вопросы, пишите в комментариях, я обязательно вам отвечу и приму все ваши замечания.

Добрый день, не могу нигде найти функцию
ПолучитьПубличнуюЧастьСертификата(Сигнатура).