📜 ⬆️ ⬇️

GIS utilities: establishing a secure connection and signing messages using WCF

Now the state information system of housing and public utilities is being actively developed, and from January 1, 2017, responsibility for managers and resource supplying organizations for failure to provide information in the system begins.

As practice shows, developers who integrate their information systems with GIS utilities spend a lot of time on establishing a secure connection and signing messages. Despite the fact that the developers of the GIS utilities provided a demo application for signing messages , I will describe how to solve this problem with the help of WCF. I want to note that in this case, additional software (such as stunnel) is not needed to establish a secure connection.

I hope this article will help developers who in the future will integrate with GIS utilities.
')
Unfortunately, without buying additional software is not enough. We need CryptoPro .NET . There is a three-month free use period. This library will provide an https connection and message signature using the XAdES-BES algorithm. Also, do not forget that you need a certificate of a qualified electronic signature.

Training


First you need to install a certificate of qualified electronic signature in the personal store of the local computer. It must be installed with the private key.
I will not describe the process in detail here, information can be found here and here .

Proxy class generation


On the site of GIS housing and public utilities in the Regulations and instructions section there is a file “Regulations and formats of information interaction of external information systems with GIS housing and public utilities”. The current version is 10.0.1.2. This file contains wsdl and xsd files, we will use them to create WCF proxy classes.

Copy all wsdl and xsd files to a folder, for example “c: / gis”. This is necessary so that the generation utility can find all the basic xsd files for which the xsd service file is used.

To generate proxy classes we will use the standard utility 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 

This command will create us a proxy class of service for getting common directories (hcs-nsi-common). This command specifies where to find the wsdl and xsd service description file, where to put the resulting cs file, and how to name the namespace. Similarly, you need to run this command for the rest of the GIS utilities services.

Add the generated proxy class to the project

Configuring the application config


Add the following sections to the application config

 <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> 

Class descriptions


In the config, we register MessageInspectorBehavior, which adds the 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); } } 

I’ll also give the listing of the SignatureMessageInspector class, which deals with the signing of messages:

 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); } } 

Using


To execute a direct request to the GIS utilities, you need to create an instance of the client of the generated proxy class, specify the client authorization settings, create the request object and call the required method:

 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); } } 

» Project Source Code

Source: https://habr.com/ru/post/311062/


All Articles