Сейчас активно дорабатывается государственная информационная система ЖКХ, а с 1 января 2017 года наступает ответственность для управляющих и ресурсоснабжающих организаций за непредоставление информации в системе.

Как показывает практика, разработчики, которые выполняют интеграцию своих информационных систем с ГИС ЖКХ, очень много времени тратят на установление защищенного соединения и подписание сообщений. Несмотря на то, что разработчиками ГИС ЖКХ предоставлено демонстрационное приложение по подписанию сообщений, я опишу как можно решить эту задачу с помощью 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. Аналогично нужно запустить эту команду для остальных сервисов ГИС ЖКХ.

Добавим сгенерированный прокси-класс в проект

Настройка конфига приложения


Добавим в конфиг приложения следующие разделы

<configuration>	
	<system.serviceModel>
		<extensions>
			<behaviorExtensions>
				<add name="MessageInspectorBehavior" type="Gis.Crypto.MessageInspectorBehaviorElement, Gis" />
			</behaviorExtensions>
		</extensions>
		<behaviors>
			<endpointBehaviors>
				<behavior name="clientCertificateConf">
					<clientCredentials>
						<!-- в блоке findValue указывается серийный ключ сертификата -->
						<clientCertificate findValue="" storeLocation="LocalMachine" x509FindType="FindBySerialNumber" />
						<serviceCertificate>
							<authentication certificateValidationMode="None" revocationMode="NoCheck" />
						</serviceCertificate>
					</clientCredentials>
					<MessageInspectorBehavior />
				</behavior>
			</endpointBehaviors>
		</behaviors>
		<bindings>
			<customBinding>
				<binding>
					<textMessageEncoding messageVersion="Soap11">
						<readerQuotas maxDepth="32" maxStringContentLength="2147483647" maxArrayLength="16348" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
					</textMessageEncoding>
					<httpsTransport authenticationScheme="Basic" useDefaultWebProxy="false" requireClientCertificate="true" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647" />
				</binding>
			</customBinding>
		</bindings>
		<client>
			<endpoint address="https://api.dom.gosuslugi.ru/ext-bus-nsi-common-service/services/NsiCommon" binding="customBinding" behaviorConfiguration="clientCertificateConf"
			          contract="Gis.Infrastructure.NsiCommonService.NsiPortsType" name="NsiCommonPort" />
		</client>
	</system.serviceModel>
</configuration>

Описание классов


В конфиге мы регистрируем 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);
	}
}

» Исходный код проекта
Поделиться с друзьями
-->

Комментарии (11)


  1. alexhott
    26.09.2016 18:31
    +1

    У нас программист по инструкции и примерам на ГИС за пару дней основные запросы выполнил.
    Потом полгода получали нужную ЭЦП через закупочные процедуры.
    Кстати Контур в Екатеринбурге с первого раза не понял что нужна ЭЦП с определенной степенью защиты, а со второго раза выдать быстро не смог, взял день на подумать и только потом мы заимели нужную ЭЦП.

    Сейчас постепенно выясняем что то один API метод то другой не работают как описано.


    1. nkochnev
      26.09.2016 18:36

      На текущем этапе стало проще разрабатывать, т.к. и примеры появились, и система стала более стабильная, и серия семинаров помогла.
      Однако, я уже морально готовлюсь, что новогодних каникул у меня не будет…


      1. DjoNIK
        26.09.2016 19:02

        Да, система пока сырая у них. И это я не с точки зрения разработчика, а как пользователь. На фидбек отвечали быстро только в том случае, если это была отписка, что я неправильно зарепортил (выбрал раздел вопрос, а не ошибка). По реальной проблеме что-то молчат уже давненько ((

        Сейчас практически неюзабельно.


  1. Ivan_83
    26.09.2016 22:57

    Лучше бы бесплатно собрали libressl и может быть чуток пропатчили и заслали в апстрим.
    Самая сложная крипта — ГОСТ2012 и гост 2001 там уже есть, хэш стрибог тоже есть, может быть только кузнечика нет и их идентов в TLS, но это мелочи.


    1. nkochnev
      27.09.2016 06:35

      Это из разряда «было бы хорошо сделать», а не «лучше бы»
      Мы немало времени потратили, чтобы сделать эту часть работы. И коллеги из других компаний спрашивали как такое провернуть. Так что не зря поделился знанием


  1. FSA
    27.09.2016 08:42
    +5

    Я вот поражаюсь тому, что говорят, мол надо от импортного отказываться. И клепают все решения с жёсткой привязкой к Microsoft Windows, а того и гляди M$ SQL. Ну и закрепляют законом обязанность использовать. В результате не только система на Windows крутится, но ещё и чтобы подключиться к ней нужен шлюз на Windows, ибо средствами Linux или BSD этого не реализовать. А на деле это всё можно было реализовать на OpenSource решениях, да ещё бы и работало стабильнее.


    1. fuCtor
      27.09.2016 08:51
      -1

      Т.к. используется криптография и взаимодействие с гос структурами, то у соответствующего ПО должны быть сертификаты и разрешения. У СПО с этим по большей части проблемы, сделать то можно, но получить разрешения на использование уже не так просто.


      1. nkochnev
        27.09.2016 09:28
        +1

        Дело не в этом.
        Если сама информационная система РКЦ, УК или РСО написана на технологиях Майкрософта, нецелесообразно использовать другой подход, чтобы сделать интеграцию с госструктурой. Проще заплатить 5000 руб за криптопро, чем нанимать сотрудника на разработку под opensource.

        А вообще разработчики ГИС ЖКХ не привязываются к конкретной технологии, просто т.к. больше всего было вопросов связанных с .NET и C#, именно под эту технологию было создано демонстрационное приложение по подписанию приложений.

        Сам ГИС ЖКХ написан на java


    1. mypomacca
      27.09.2016 10:27

      Это да, еще бы аналог зарубежного ПО был нормальный… а то одни слезы, взять к примеру ROSA Enterprise Linux.


  1. shifttstas
    27.09.2016 13:58
    +1

    Ужас какой, вендор лок так еще и проприаритарные системы под которые сложно\дорого разрабатывать.


  1. ulvham
    28.09.2016 10:34

    В данном случае (как всегда) не так страшен ГИС, как его внедрение.
    Подробнее с точки зрения РСО:
    — Чтобы работал ГИС, нужно, чтобы работал ФИАС. Чтобы работал ФИАС, нужно найти тех, кто его заполняет… пока не удалось...(искали в куче разных регионов… никто так и не нашёл...)
    — Государство говорит «Вы обязаны заполнять данные в ГИС...», про то в каком виде в самом крупном РСО в стране хранятся данные им наплевать. Никого не спросили ни на стадии проектирования, ни на стадии подписания нормативных документов.

    По факту сделали обычный сайт с интеграцией в ЕСИА, а вони развели на всю страну…