В соответствии с требованиями, обозначенными в приказе Министерства связи и массовых коммуникаций Российской Федерации от 27.12.2010 №190 от «Об утверждении Технических требований к взаимодействию информационных систем в единой системе межведомственного электронного взаимодействия» (полный текст приказа доступен здесь) и Методическими рекомендациями по разработке электронных сервисов и применению технологии электронной подписи при межведомственном электронном взаимодействии (скачать документ можно здесь), при интеграции информационных систем в систему межведомственного электронного взаимодействия (далее СМЭВ) необходимо среди прочего придерживаться спецификаций на протокол обмена структурированными сообщениями (Simple Object Access Protocol, SOAP) версии 1.1, расширяемый язык разметки (Extensible Markup Language, XML) и расширяемый язык описания схем данных версии не ниже 1.0 (XML Schema 1.0/1.1). Там же описываются требования к структуре электронных сообщений в СМЭВ.
В связи с многочисленными обращениями пользователей с вопросами по взаимодействию со СМЭВ с использованием наших продуктов мы подготовили ряд примеров, демонстрирующих эту функциональность. В данной статье рассматривается простой способ создания подписанных сообщений для СМЭВ и проверки подписи в сообщениях от СМЭВ с использованием платформы Microsoft .NET Framework.
Данный пример написан на языке C# и основан на использовании методов класса SignedXml. Создание тела сообщения и разбор ответа СМЭВ оставлены за рамками примера. Для работы примера необходимо установкить СКЗИ КриптоПро CSP 3.6 R2 и КриптоПро .NET, а также иметь сертификат подписи (пробный сертификат можно получить на нашем тестовом центре).
Ближе к делу!
За основу взят первый пример из статьи про SignedXml, который создаёт и проверяет приложенную (enveloped) подпись.
На вход необходимо подать документ XML примерно следующего вида:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"> <S:Header> <wsse:Security S:actor="http://smev.gosuslugi.ru/actors/smev"> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <ds:KeyInfo> <wsse:SecurityTokenReference> <wsse:Reference URI="#SenderCertificate"/> </wsse:SecurityTokenReference> </ds:KeyInfo> </ds:Signature> <wsse:BinarySecurityToken EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" wsu:Id="SenderCertificate"> </wsse:BinarySecurityToken> </wsse:Security> </S:Header> <S:Body wsu:Id="body"> <!-- Здесь идёт тело запроса--> </S:Body> </S:Envelope>
Первое, что необходимо изменить в коде исходного примера, это создание объекта класса SignedXml
. Метод GetIdElement этого класса умеет искать узлы только по атрибуту Id
в глобальном простанстве имен, тогда как подписываемое тело сообщения для СМЭВ должно быть помечено атрибутом wsu:Id
. Для перегрузки этого метода создаём новый класс:
class SmevSignedXml : SignedXml { public SmevSignedXml(XmlDocument document) : base(document) { } public override XmlElement GetIdElement(XmlDocument document, string idValue) { XmlNamespaceManager nsmgr = new XmlNamespaceManager(document.NameTable); nsmgr.AddNamespace("wsu", WSSecurityWSUNamespaceUrl); return document.SelectSingleNode("//*[@wsu:Id='" + idValue + "']", nsmgr) as XmlElement; } }
Заменяем создание объекта:
SmevSignedXml signedXml = new SmevSignedXml(doc);
Ключ подписи возьмём не случайный, как в исходном примере, а из сертификата:
signedXml.SigningKey = Certificate.PrivateKey;
Подписывать будем только один элемент XML, поэтому в ссылке указываем его идентификатор. В данном примере и в методических рекомендациях СМЭВ подписываемый узел soapenv:Body
помечен идентификатором "body":
Reference reference = new Reference(); reference.Uri = "#body";
Задаём алгоритм хэширования подписываемого узла - ГОСТ Р 34.11-94. Необходимо использовать устаревший идентификатор данного алгоритма, т.к. именно такой идентификатор используется в СМЭВ:
reference.DigestMethod = CryptoPro.Sharpei.Xml.CPSignedXml.XmlDsigGost3411UrlObsolete;
Преобразование (transform) для создания приложенной подписи в данном примере не нужно. Для СМЭВ необходимо добавить преобразование, приводящее подписываемый узел к каноническому виду по алгоритму http://www.w3.org/2001/10/xml-exc-c14n#:
XmlDsigExcC14NTransform c14 = new XmlDsigExcC14NTransform(); reference.AddTransform(c14);
Задаём преобразование для приведения узла ds:SignedInfo
к каноническому виду по алгоритму http://www.w3.org/2001/10/xml-exc-c14n# в соответствии с методическими рекомендациями СМЭВ:
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
Задаём алгоритм подписи - ГОСТ Р 34.10-2001. Необходимо использовать устаревший идентификатор данного алгоритма, т.к. именно такой идентификатор используется в СМЭВ:
signedXml.SignedInfo.SignatureMethod = CryptoPro.Sharpei.Xml.CPSignedXml.XmlDsigGost3410UrlObsolete;
После вычисления подписи вместо добавления полученного узла ds:Signature
в документ целиком необходимо взять лишь некоторые подузлы и вставить их в заготовленное место:
doc.GetElementsByTagName("ds:Signature")[0].PrependChild( doc.ImportNode(xmlDigitalSignature.GetElementsByTagName("SignatureValue")[0], true)); doc.GetElementsByTagName("ds:Signature")[0].PrependChild( doc.ImportNode(xmlDigitalSignature.GetElementsByTagName("SignedInfo")[0], true));
Остаётся лишь добавить сертификат подписи в заготовленный узел wsse:BinarySecurityToken
:
doc.GetElementsByTagName("wsse:BinarySecurityToken")[0].InnerText = Convert.ToBase64String(Certificate.RawData);
Вуаля! Теперь документ doc
можно отправлять в СМЭВ любым удобным способом.
Теперь рассмотрим проверку подписи. Для проверки вместо класса SingedXml
также необходимо использовать класс SmevSignedXml
. После загрузки узла с подписью в соответствующий объект ищем по ссылке узел wsse:BinarySecurityToken
, содержащий сертификат подписи:
XmlNodeList referenceList = signedXml.KeyInfo.GetXml().GetElementsByTagName( "Reference", WSSecurityWSSENamespaceUrl); if (referenceList.Count == 0) { throw new XmlException("Не удалось найти ссылку на сертификат"); } // Ищем среди аттрибутов ссылку на сертификат. string binaryTokenReference = ((XmlElement)referenceList[0]).GetAttribute("URI"); // Ссылка должна быть на узел внутри данного документа XML, т.е. она имеет вид // #ID, где ID - идентификатор целевого узла if (string.IsNullOrEmpty(binaryTokenReference) || binaryTokenReference[0] != '#') { throw new XmlException("Не удалось найти ссылку на сертификат"); } // Получаем узел BinarySecurityToken с закодированным в base64 сертификатом XmlElement binaryTokenElement = signedXml.GetIdElement( xmlDocument, binaryTokenReference.Substring(1)); if (binaryTokenElement == null) { throw new XmlException("Не удалось найти сертификат"); }
Остаётся лишь создать объект класса X509Certificate2
и проверить подпись с помощью соответствующего открытого ключа:
X509Certificate2 cert = new X509Certificate2(Convert.FromBase64String(binaryTokenElement.InnerText)); bool result = signedXml.CheckSignature(cert.PublicKey.Key);
Полный текст примера можно найти в составе КриптоПро .NET SDK.
А что дальше?
Рассмотренный способ выполнения требований по общению со СМЭВ с использованием класса SignedXml
прост и понятен, но подготовка запросов и ответов к веб-сервисам СМЭВ, оставленная за рамками примера, может стать дополнительной головной болью для разработчиков. Для упрощения этих этапов предназначены специализированные средства для создания веб-сервисов и клиентских приложений к ним, такие как Windows Communication Foundation (WCF). В WCF встроена поддержка защищённых способов взаимодействия с веб-сервисами, к которым относится в том числе и подпись запросов и ответов, используемая в СМЭВ.
В ближайшее время на страницах нашего блога также будут опубликованы статьи с описанием примеров взаимодействия с сервисами СМЭВ с использованием WCF и средств платформы Java. Так что следите за обновлениями! Проще всего это сделать, подписавшись на RSS-поток этого блога.
Алексей Голдбергс
Павел Смирнов
Максим Коллегин
Михаил Хоменко
* Благодарим наших партнеров, ООО Удостоверяющий центр «АСКОМ» за предоставленные материалы, которые были использованы при написании данной статьи.