В соответствии с требованиями, обозначенными в приказе Министерства связи и массовых коммуникаций Российской Федерации от 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-поток этого блога.
Алексей Голдбергс
Павел Смирнов
Максим Коллегин
Михаил Хоменко
* Благодарим наших партнеров, ООО Удостоверяющий центр «АСКОМ» за предоставленные материалы, которые были использованы при написании данной статьи.