ГИС ЖКХ: установка защищенного соединения и подписание сообщений с помощью WCF
Как показывает практика, разработчики, которые выполняют интеграцию своих информационных систем с ГИС ЖКХ, очень много времени тратят на установление защищенного соединения и подписание сообщений. Несмотря на то, что разработчиками ГИС ЖКХ предоставлено демонстрационное приложение по подписанию сообщений, я опишу как можно решить эту задачу с помощью WCF. Хочу отметить, что в этом случае дополнительное ПО (типа stunnel) для установки защищенного соединения не нужно.
Надеюсь, эта статья поможет разработчикам, которые в будущем будут интегрироваться с ГИС ЖКХ.
К сожалению, без покупки дополнительного программного обеспечения не обойтись. Нам нужен КриптоПро .NET. Есть трёхмесячный бесплатный срок использования. Эта библиотека будет обеспечивать https соединение и подпись сообщений по алгоритму XAdES-BES. Также не забываем, что нужен сертификат квалифицированной электронной подписи.
Подготовка
Для начала нужно установить сертификат квалифицированной электронной подписи в личное хранилище локального компьютера. Он должен быть установлен вместе с закрытым ключом.
Подробно описывать процесс тут не буду, информацию можно найти тут и тут.
Генерация прокси-классов
На сайте ГИС ЖКХ в разделе Регламенты и инструкции находится файл «Регламент и форматы информационного взаимодействия внешних информационных систем с ГИС ЖКХ». Текущая версия 10.0.1.2. В этом файле находятся wsdl и xsd файлы, мы их будем использовать для создания прокси-классов WCF.
Скопируем все wsdl и xsd файлы в какую-нибудь папку, например «c:/gis». Это нужно для того, чтобы утилита генерации могла найти все базовые xsd файлы, на которые использует xsd файл сервиса.
Для генерации прокси-классов мы будем использовать стандартную утилиту SvcUtil.
"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\SvcUtil.exe" c:/gis/hcs-nsi-common-service.wsdl c:/gis/*.xsd /messageContract /enableDataBinding /syncOnly /directory:"c:/gis/proxies" /noConfig /noLogo /out:NsiCommonService.cs /namespace:*,Gis.Infrastructure.NsiCommonService
Эта команда создаст нам прокси-класс сервиса для получения общих справочников (hcs-nsi-common). В этой команде указано, где найти wsdl и xsd файл описания сервиса, куда положить результирующий cs файл и как назвать namespace. Аналогично нужно запустить эту команду для остальных сервисов ГИС ЖКХ.
Добавим сгенерированный прокси-класс в проект
Настройка конфига приложения
Добавим в конфиг приложения следующие разделы
Описание классов
В конфиге мы регистрируем MessageInspectorBehavior, который добавляет ClientMessageInspector:
public class MessageInspectorBehavior : IEndpointBehavior
{
public void Validate(ServiceEndpoint endpoint)
{
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
SignatureMessageInspector inspector =
new SignatureMessageInspector();
clientRuntime.MessageInspectors.Add(inspector);
}
}
Ещё приведу листинг класса SignatureMessageInspector, который и занимается подписанием сообщений:
public class SignatureMessageInspector : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
string st = GetSignElement(MessageString(ref request));
//place for log request
request = CreateMessageFromString(st, request.Version);
return null;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
string st = MessageString(ref reply);
//place for log response
reply = CreateMessageFromString(st, reply.Version);
}
public static string GetSignElement(string messageString)
{
var originalDoc = new XmlDocument { PreserveWhitespace = true };
originalDoc.LoadXml(messageString);
var nodes = originalDoc.SelectNodes($"//node()[@Id='{CryptoConsts.CONTAINER_ID}']");
if (nodes == null || nodes.Count == 0)
{
return originalDoc.OuterXml;
}
var gostXadesBesService = new GostXadesBesService();
string st = gostXadesBesService.Sign(messageString, CryptoConsts.CONTAINER_ID, CryptoConsts.CERTIFICATE_THUMBPRINT, string.Empty);
return st;
}
Message CreateMessageFromString(String xml, MessageVersion ver)
{
return Message.CreateMessage(XmlReaderFromString(xml), int.MaxValue, ver);
}
XmlReader XmlReaderFromString(String xml)
{
var stream = new MemoryStream();
// NOTE: don't use using(var writer ...){...}
// because the end of the StreamWriter's using closes the Stream itself.
//
var writer = new StreamWriter(stream);
writer.Write(xml);
writer.Flush();
stream.Position = 0;
return XmlReader.Create(stream);
}
String MessageString(ref Message m)
{
// copy the message into a working buffer.
MessageBuffer mb = m.CreateBufferedCopy(int.MaxValue);
// re-create the original message, because "copy" changes its state.
m = mb.CreateMessage();
Stream s = new MemoryStream();
XmlWriter xw = XmlWriter.Create(s);
mb.CreateMessage().WriteMessage(xw);
xw.Flush();
s.Position = 0;
byte[] bXml = new byte[s.Length];
s.Read(bXml, 0, (int) s.Length);
// sometimes bXML[] starts with a BOM
if (bXml[0] != (byte) '<')
{
return Encoding.UTF8.GetString(bXml, 3, bXml.Length - 3);
}
return Encoding.UTF8.GetString(bXml, 0, bXml.Length);
}
}
Использование
Для выполнения непосредственного запроса к ГИС ЖКХ необходимо создать экземпляр клиента сгенерированного прокси-класса, указать настройки клиентской авторизации, сформировать объект запроса и вызвать необходимый метод:
class Program
{
static void Main(string[] args)
{
var service = new NsiPortsTypeClient();
service.ClientCredentials.UserName.UserName = "lanit";
service.ClientCredentials.UserName.Password = "tv,n8!Ya";
var request = new exportNsiListRequest1
{
ISRequestHeader = new HeaderType
{
Date = DateTime.Now,
MessageGUID = Guid.NewGuid().ToString()
},
exportNsiListRequest = new exportNsiListRequest
{
version = "10.0.1.2",
ListGroup = ListGroup.NSI,
ListGroupSpecified = true,
Id = CryptoConsts.CONTAINER_ID
}
};
var result = service.exportNsiList(request);
}
}
» Исходный код проекта
Комментарии (4)
26 сентября 2016 в 18:31
0↑
↓
У нас программист по инструкции и примерам на ГИС за пару дней основные запросы выполнил.
Потом полгода получали нужную ЭЦП через закупочные процедуры.
Кстати Контур в Екатеринбурге с первого раза не понял что нужна ЭЦП с определенной степенью защиты, а со второго раза выдать быстро не смог, взял день на подумать и только потом мы заимели нужную ЭЦП.Сейчас постепенно выясняем что то один API метод то другой не работают как описано.
26 сентября 2016 в 18:36
0↑
↓
На текущем этапе стало проще разрабатывать, т.к. и примеры появились, и система стала более стабильная, и серия семинаров помогла.
Однако, я уже морально готовлюсь, что новогодних каникул у меня не будет…26 сентября 2016 в 19:02
0↑
↓
Да, система пока сырая у них. И это я не с точки зрения разработчика, а как пользователь. На фидбек отвечали быстро только в том случае, если это была отписка, что я неправильно зарепортил (выбрал раздел вопрос, а не ошибка). По реальной проблеме что-то молчат уже давненько ((Сейчас практически неюзабельно.
26 сентября 2016 в 22:57
0↑
↓
Лучше бы бесплатно собрали libressl и может быть чуток пропатчили и заслали в апстрим.
Самая сложная крипта — ГОСТ2012 и гост 2001 там уже есть, хэш стрибог тоже есть, может быть только кузнечика нет и их идентов в TLS, но это мелочи.