Подготовка SOAP-сообщения перед созданием подписи XMLDSIG на примере СМЭВ

Проверка подписи органа власти с помощью сервиса СМЭВ
Проверка подписи XMLDSIG на примере СМЭВ

Для создания электронной подписи SOAP-сообщения используется WS-Security – это протокол передачи подписанных SOAP-сообщений. Основная его особенность заключается в том, что он работает на транспортном уровне. При разработке Web-сервисов и передаче их WSDL-документов сторонним разработчикам, в большинстве случаев описывается только тело входящего и исходящего SOAP-сообщения, в котором описана вся бизнес-структура передаваемых данных в формате XML. При применении стандарта WS-Security, электронная подпись передается в заголовке SOAP-сообщения, таким образом, в заголовок добавляется XML-структура, в которой содержится электронная подпись, подписывающая данные, находящиеся в теле SOAP-сообщения.
Структура электронной подписи SOAP-сообщения
Создание подписи происходит следующим образом:
  • Формируется основа SOAP-сообщения, узлы Header и Body;
  • Тело SOAP-сообщения заполняется данными и добавляется атрибут Id с уникальным значением - идентификатор подписываемых данных;
  • Подготавливается структура Security и включается в заголовок SOAP-сообщения.
Напишем функцию, которая подготовит структуру Security и вставит в заголовок SOAP-сообщения на платформе 1С:

// Функция формирует шаблон WS-Security в заголовке SOAP-сообщения для создания подписи XMLDSIG.
//
// Параметры
//  СтрокаXML  - Строка - SOAP-сообщение.
//
// Возвращаемое значение:
//   Строка   - SOAP-сообщение + WS-Security.
//
Функция СоздатьСтруктуруWSSecurityДляSOAPсообщения(СтрокаXML) Экспорт

///////////////////////////////////////////////////
// Подготовка

ИД = Строка(Новый УникальныйИдентификатор);
ДанныеИД = "body";

ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(СтрокаXML);
ПостроительDOM = Новый ПостроительDOM;
ДокументDOM = ПостроительDOM.Прочитать(ЧтениеXML);

Корень = ДокументDOM.ЭлементДокумента;
ПрефиксСОАП = Корень.НайтиПрефикс("http://schemas.xmlsoap.org/soap/envelope/");

// Добавим пространство имен "wsse" для спецификации WS-Security.
ПространствоИменСекрет = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
ПрефиксСекрет = "wsse";
УзелDOM = ДокументDOM.СоздатьАтрибут("http://www.w3.org/2000/xmlns/", "xmlns:" + ПрефиксСекрет);
УзелDOM.Значение = ПространствоИменСекрет;
Корень.Атрибуты.УстановитьИменованныйЭлемент(УзелDOM);

// Добавим пространство имен "wsu" для спецификации WS-Security.
ПространствоИменУтилити = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
ПрефиксУтилити = "wsu";
УзелDOM = ДокументDOM.СоздатьАтрибут("http://www.w3.org/2000/xmlns/", "xmlns:" + ПрефиксУтилити);
УзелDOM.Значение = ПространствоИменУтилити;
Корень.Атрибуты.УстановитьИменованныйЭлемент(УзелDOM);

// URI пространства имен сецификации XML Signature.
ПространствоИменЭП = "http://www.w3.org/2000/09/xmldsig#";

///////////////////////////////////////////////////
// Работа с Header

Header = Корень.ПолучитьЭлементыПоИмени(ПрефиксСОАП, "Header")[0];

// Security - узел, который будет хранить подпись XMLDSIG.
ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменСекрет, ПрефиксСекрет + ":Security");
УзелDOM = ДокументDOM.СоздатьАтрибут("http://schemas.xmlsoap.org/soap/envelope/", ПрефиксСОАП + ":mustUnderstand");
УзелDOM.Значение = "1";
ЭлементDOM.УстановитьУзелАтрибута(УзелDOM);
УзелDOM = ДокументDOM.СоздатьАтрибут("http://schemas.xmlsoap.org/soap/envelope/", ПрефиксСОАП + ":actor");
УзелDOM.Значение = "http://smev.gosuslugi.ru/actors/smev";
ЭлементDOM.УстановитьУзелАтрибута(УзелDOM);
Security = Header.ДобавитьДочерний(ЭлементDOM);

// BinarySecurityToken - узел, который будет хранить открытый ключ.
ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменСекрет, ПрефиксСекрет + ":BinarySecurityToken");
ТекстDOM = ДокументDOM.СоздатьТекстовыйУзел("#Certificate_ENCODE_BASE64");
ЭлементDOM.ДобавитьДочерний(ТекстDOM);
УзелDOM = ДокументDOM.СоздатьАтрибут(ПространствоИменУтилити, ПрефиксУтилити + ":Id");
УзелDOM.Значение = ИД;
ЭлементDOM.УстановитьУзелАтрибута(УзелDOM);
УзелDOM = ДокументDOM.СоздатьАтрибут("ValueType");
УзелDOM.Значение = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3";
ЭлементDOM.УстановитьУзелАтрибута(УзелDOM);
УзелDOM = ДокументDOM.СоздатьАтрибут("EncodingType");
УзелDOM.Значение = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary";
ЭлементDOM.УстановитьУзелАтрибута(УзелDOM);
BinarySecurityToken = Security.ДобавитьДочерний(ЭлементDOM);

// Signature - узел, который будет хранить информацию о том, что было подписано,
// подпись, ключи, используемые для создания подписи,
// и место для сохранения произвольных данных.
ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "Signature");
Signature = Security.ДобавитьДочерний(ЭлементDOM);

// SignedInfo - информация о подписываемых элементах.
ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "SignedInfo");
SignedInfo = Signature.ДобавитьДочерний(ЭлементDOM);

// CanonicalizationMethod - алгоритм приведения к каноническому виду.
// Для СМЭВ "http://www.w3.org/2001/10/xml-exc-c14n#".
ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "CanonicalizationMethod");
УзелDOM = ДокументDOM.СоздатьАтрибут("Algorithm");
УзелDOM.Значение = "http://www.w3.org/2001/10/xml-exc-c14n#";
ЭлементDOM.УстановитьУзелАтрибута(УзелDOM);
SignedInfo.ДобавитьДочерний(ЭлементDOM);

// SignatureMethod - идентификатор алгоритма подписи.
// Для СМЭВ "http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411".
ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "SignatureMethod");
УзелDOM = ДокументDOM.СоздатьАтрибут("Algorithm");
УзелDOM.Значение = "http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411";
ЭлементDOM.УстановитьУзелАтрибута(УзелDOM);
SignedInfo.ДобавитьДочерний(ЭлементDOM);

// Reference - атрибут URI, содержащий ссылку на подписываемый элемент в документе.
ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "Reference");
УзелDOM = ДокументDOM.СоздатьАтрибут("URI");
УзелDOM.Значение = "#" + ДанныеИД;
ЭлементDOM.УстановитьУзелАтрибута(УзелDOM);
Reference = SignedInfo.ДобавитьДочерний(ЭлементDOM);

// Transforms - преобразования, которые следует применить к подписываемым элементам.
// В данном случае - приведение к каноническому виду.
ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "Transforms");
Transforms = Reference.ДобавитьДочерний(ЭлементDOM);

// Transform - см. описание Transforms.
ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "Transform");
УзелDOM = ДокументDOM.СоздатьАтрибут("Algorithm");
УзелDOM.Значение = "http://www.w3.org/2001/10/xml-exc-c14n#";
ЭлементDOM.УстановитьУзелАтрибута(УзелDOM);
Transforms.ДобавитьДочерний(ЭлементDOM);

// DigestMethod - идентификатор алгоритма хэширования.
// Для СМЭВ "http://www.w3.org/2001/04/xmldsig-more#gostr3411".
ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "DigestMethod");
УзелDOM = ДокументDOM.СоздатьАтрибут("Algorithm");
УзелDOM.Значение = "http://www.w3.org/2001/04/xmldsig-more#gostr3411";
ЭлементDOM.УстановитьУзелАтрибута(УзелDOM);
Reference.ДобавитьДочерний(ЭлементDOM);

// DigestValue - узел будет содержать хэш-значение подписываемых элементов.
// Данный элемент следует оставить пустым.
// Его значение будет заполнено при создании подписи.
ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "DigestValue");
Reference.ДобавитьДочерний(ЭлементDOM);

// SignatureValue - узел будет содержать значение подписи.
// Данный элемент следует оставить пустым.
// Его значение будет заполнено при создании подписи.
ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "SignatureValue");
SignatureValue = Signature.ДобавитьДочерний(ЭлементDOM);

// KeyInfo - узел будет содержать информацию о сертификате ключа подписи.
ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменЭП, "KeyInfo");
KeyInfo = Signature.ДобавитьДочерний(ЭлементDOM);

// SecurityTokenReference - узел будет содержать ссылку на сертификат.
ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменСекрет, ПрефиксСекрет + ":SecurityTokenReference");
SecurityTokenReference = KeyInfo.ДобавитьДочерний(ЭлементDOM);

// Reference - атрибут ValueType содержит значение
// "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3".
// Атрибут URI содержит ссылку на уникальный идентификатор
// сертификата (такой же, как указан в элементе BinarySecurityToken).
ЭлементDOM = ДокументDOM.СоздатьЭлемент(ПространствоИменСекрет, ПрефиксСекрет + ":Reference");
УзелDOM = ДокументDOM.СоздатьАтрибут("ValueType");
УзелDOM.Значение = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3";
ЭлементDOM.УстановитьУзелАтрибута(УзелDOM);
УзелDOM = ДокументDOM.СоздатьАтрибут("URI");
УзелDOM.Значение = "#" + ИД;
ЭлементDOM.УстановитьУзелАтрибута(УзелDOM);
SecurityTokenReference.ДобавитьДочерний(ЭлементDOM);

///////////////////////////////////////////////////
// Работа с Body

Body = Корень.ПолучитьЭлементыПоИмени(ПрефиксСОАП, "Body")[0];
УзелDOM = ДокументDOM.СоздатьАтрибут(ПространствоИменУтилити, ПрефиксУтилити + ":Id");
УзелDOM.Значение = ДанныеИД;
Body.УстановитьУзелАтрибута(УзелDOM);

///////////////////////////////////////////////////
// Формирование XML

ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку(Новый ПараметрыЗаписиXML("UTF-8"));
ЗаписьXML.ЗаписатьОбъявлениеXML();
ЗаписьDOM = Новый ЗаписьDOM;
ЗаписьDOM.Записать(Корень, ЗаписьXML);
СформированныйДокумент = ЗаписьXML.Закрыть();

Возврат СформированныйДокумент;

КонецФункции // СоздатьСтруктуруWSSecurityДляSOAPсообщения()


Описывать работу функции не имеет смысла, в коде присутствую комментарии. Параметр СтрокаXML должен принимать SOAP-сообщение:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header />
<SOAP-ENV:Body>
<!-- Данные -->
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Обработку можно скачать по этой ссылке.

Пример работы обработки:
Формирование структуры WS-Security для SOAP-сообщения
Далее сформированное SOAP-сообщение с WS-Security в заголовке можно подписать с помощью КриптоПро CADESCOM.

Комментарии

  1. Следующий код мне кажется более читабельным. Делает то же самое.

    http://ps.tmpc.ru/282e28e7cfc8

    Комментарии ограничены 4096-ю символами, поэтому код помещен на внешний ресурс.

    ОтветитьУдалить
  2. Кстати, в Библиотеке стандартных подсистем есть функция ЭлектроннаяПодписьКлиент.Подписать, которая умеет подписывать сообщения SOAP с использованием XMLDSig. Так что, можно всё делать в коде 1С.

    ОтветитьУдалить
    Ответы
    1. Да, верно подметили, но я вычислил 2 НО:
      1) Не годится для СМЭВ 3 (СМЭВовцы добавили свою трансформацию)
      2) Так и не получилось запустить на стороне клиента (подключаемая библиотека вылетала в ошибку, может уже и исправили проблему)

      Удалить
  3. Отлично то что нужно , а как ее подписать теперь????

    ОтветитьУдалить

Отправить комментарий