Подпись сообщений SOAP для СМЭВ с использованием КриптоПро .NET

В соответствии с требованиями, обозначенными в приказе Министерства связи и массовых коммуникаций Российской Федерации от 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-поток этого блога.

Алексей Голдбергс

Павел Смирнов

Максим Коллегин

Михаил Хоменко

* Благодарим наших партнеров, ООО Удостоверяющий центр «АСКОМ» за предоставленные материалы, которые были использованы при написании данной статьи.

WCF для СМЭВ

Здравствуйте, Вы обещали выложить статью про создание службы WCF для СМЭВ, но в блоге её нет.

"Вот статья про wcf сервис
http://mustiksprogramming.blogspot.com/2012/09/SmevWcf.html"

Эта ссылка уже не актуальная.
У кого есть статьи или материалы по WCF для СМЭВ - поделитесь пожалуйста

отсутвует ссылка на полный пример

Вы пишите что - Полный текст примера можно найти в составе КриптоПро .NET SDK.
А самого примера нету!

Пример подписи запроса к СМЭВ

Полный текст примера можно найти в КриптоПро .NET SDK:
%ProgramFiles(x86)%\Crypto Pro\.NET SDK\Examples\simple.zip
В проекте смотрите файл SignSmevRequest.cs

В коде есть пример получения сертификата подписи запроса.

Для использования классов, констант и т.п. из пространства имён CryptoPro.Sharpei.Xml необходимо добавить ссылку на библиотеку CryptoPro.Sharpei.Xml.dll (смотреть ссылки в проекте с примерами).

Так же в тексте примера показано как получить xmlDigitalSignature.

Не получается повторить данный пример

Вот вы в статье указываете:

Ключ подписи возьмём не случайный, как в исходном примере, а из сертификата:
signedXml.SigningKey = Certificate.PrivateKey;

а как получить сам Certificate ???
подскажите ссылки на примеры, как работать на C# ?

И еще:

Задаём алгоритм хэширования подписываемого узла - ГОСТ Р 34.11-94. Необходимо использовать устаревший идентификатор данного алгоритма, т.к. именно такой идентификатор используется в СМЭВ:

reference.DigestMethod =
CryptoPro.Sharpei.Xml.CPSignedXml.XmlDsigGost3411UrlObsolete;

а как получить CryptoPro ???
Подскажите примеры?

После вычисления подписи вместо добавления полученного узла 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));

а как получить xmlDigitalSignature ???
есть примеры ?

Хотелось бы разобраться...

подпись для СМЭВ с помощью cadesplugin

Подскажите, пожалуйста- взял пример http://cpdn.cryptopro.ru/?url=/content/cades/plugin-samples-sign.html
выдаёт ошибку:
Failed to create signature. Error: Unknown error code (0x800705BA) (-2147023430)
на вызове
sSignedMessage = oSignedXML.Sign(oSigner);
в чём может быть дело?

Реализация ЭЦП для СМЭВ в Delphi

А будет ли пример реализации ЭЦП в старом, добром Delphi?

Константин

а на Python это можно реализовать?

WCF

Очень ждем новой статьи!!! Спасибо Вам!

Wcf и Крипто-Про

Ребята не подскажите когда приблизительно сможете выложить пример подписывания документа со стороны сервера с использованием Wcf? Очень помогают ваши статьи в разработке, спасибо=)

Вот статья про wcf

Вот статья про wcf сервис
http://mustiksprogramming.blogspot.com/2012/09/SmevWcf.html

Когда будет статья с WCF?

Сейчас как раз пытаемся реализовать у себя в системе. Система разработана на .NET 4.0

Подскажите, когда будет статья про взаимодействие по СМЭВ с использованием WCF?

Заранее большое спасибо!

Java

Вы пишите "В ближайшее время на страницах нашего блога также будут опубликованы статьи с описанием примеров взаимодействия с сервисами СМЭВ с использованием WCF и средств платформы Java."

Когда планируется выпуск статьи для платформы Java?
жду с нестерпением :-)

хотелось бы пример для Java 6

за ранее благодарен!!!

Java: СМЭВ с использованием КриптоПро JCP

Купить

Форма заказа

Вход

Подписка на обновления