📜 ⬆️ ⬇️

Interaction with GIS utilities using stunnel and openssl according to GOST



The task of adjusting interaction with the GIS housing and communal services stood in front of us in full growth. According to the documentation , it is supposed to use the well-known domestic software to encrypt the tunnel and form an EDS according to GOST, but this is not our method. Armed with Google and the console, I and slavam implemented the necessary functionality with improvised means.
All the necessary software is on both Linux and Windows platforms, because the technique can be called multiplatform.


')
  1. Training
  2. Crypto Tunnel according to GOST
  3. XMLDSig generation
  4. Links


Training



The interaction consists of the following parts:

To work using the server CentOS 6, Python 2.7.11, OpenSSL + GOST engine, stunnel. Brief installation instructions openssl with GOST support
for the lazy:
# wget https://www.openssl.org/source/openssl-1.0.2g.tar.gz # tar -xvf openssl-1.0.2g.tar.gz # cd openssl-1.0.2g/ # yum groupinstall "Development Tools" # yum install zlib zlib-devel # ./config shared zlib enable-rfc3779 # make && make install # echo "/usr/local/ssl/lib/" > /etc/ld.so.conf.d/openssl.conf # ldconfig # /usr/local/ssl/bin/openssl ciphers | tr ":" "\n" | grep -i gost GOST2001-GOST89-GOST89 GOST94-GOST89-GOST89 # cat /usr/local/ssl/openssl.cnf ………… openssl_conf = openssl_def [openssl_def] engines = engine_section [engine_section] gost = gost_section [gost_section] engine_id = gost default_algorithms = ALL ………… 


I think nothing complicated should be and everything has already been described hundreds of times. And in more recent distributions, the gost module is out of the box.

OpenSSL is used in two ways: stunnel is built with it, so that it works according to GOST; and the openssl utility is called from the python code to generate and verify signatures. The openssl call was caused by the fact that it was not possible to set the used crypto module from the python code. All the necessary software is on both Linux and Windows platforms, because the technique can be called multiplatform.

For python server CentOS 6 you will need to update the lxml library, and to do this put a few additional packages:
 # yum install libxml2 libxml2-devel libxslt libxslt-devel python-devel # pip install lxml --upgrade 


We also need the certificate and private key files in the PKCS12 (.pem) format. You can get them from eToken using tools like P12FromGostCSP or manually . If this is not done for any reason, then there is the option of working with the “Rutoken EDS” key directly . The site has detailed instructions on how to teach this OpenSSL and stunnel. Thus, the task is reduced to the previous one. I did not have such a key at hand, so I could not verify it.

Crypto Tunnel according to GOST


The tunnel is raised using Stunnel , which works in proxy mode. You just need to teach him to use the gost module. But there is a moment - in order for the module to initialize correctly, you need to slightly fix the source code. The problem, as I understand it, is related to the order of initialization of the module and methods. So:
 # wget https://www.stunnel.org/downloads/stunnel-5.31.tar.gz # tar -xvf stunnel-5.31.tar.gz # cd stunnel-5.31 

Edit the src / options.c file and add a call to SSL_library_init (); at the end of the " NOEXPORT char *engine_init(void) " function; .
It turns out something like this:
 NOEXPORT char *engine_init(void) { if(engine_initialized) /* either first or already initialized */ return NULL; /* OK */ s_log(LOG_DEBUG, "Initializing engine #%d (%s)", current_engine+1, ENGINE_get_id(engines[current_engine])); if(!ENGINE_init(engines[current_engine])) { if(ERR_peek_last_error()) /* really an error */ sslerror("ENGINE_init"); else s_log(LOG_ERR, "Engine #%d (%s) not initialized", current_engine+1, ENGINE_get_id(engines[current_engine])); return "Engine initialization failed"; } #if 0 /* it is a bad idea to set the engine as default for all sections */ /* the "engine=auto" or "engineDefault" options should be used instead */ if(!ENGINE_set_default(engines[current_engine], ENGINE_METHOD_ALL)) { sslerror("ENGINE_set_default"); return "Selecting default engine failed"; } #endif s_log(LOG_INFO, "Engine #%d (%s) initialized", current_engine+1, ENGINE_get_id(engines[current_engine])); SSL_library_init(); engine_initialized=1; return NULL; /* OK */ } 


The solution was found here .
We assemble with the connection of previously assembled OpenSSL:
 # ./configure --with-ssl=/usr/local/ssl --disable-libwrap # make && make install 


Configuration file /etc/stunnel.conf :
 client=yes #    (). CAFile=/etc/crypto/CA-SIT.pem engine=gost sslVersion=TLSv1 engineDefault = ALL output=/var/log/stunnel.log DEBUG=4 #   eToken-. cert=/etc/crypto/public.pem key=/etc/crypto/private.key [pseudo-https] #    ,    . accept = 10.1.5.133:8080 #    ,    (). connect = 54.76.42.99:60045 ciphers = GOST2001-GOST89-GOST89 

We put the certificate files in the / etc / crypto / directory. In general, stunnel can work under any user, but let us be root.
The simplest init.d service control script:
 #! /bin/bash # # stunnel Start/Stop Stunnel # # chkconfig: 2345 90 60 # description: launches Stunnel # processname: stunnel # config: /etc/stunnel.conf # Source function library. . /etc/init.d/functions # See how we were called. prog="Stunnel CryptoTunnel" RNG=PROGRAM export RNG start() { echo -n $"Starting $prog: " /usr/local/bin/stunnel /etc/stunnel.conf RETVAL=$? [ $RETVAL -eq 0 ] && success [ $RETVAL -ne 0 ] && failure echo return $RETVAL } stop() { echo -n $"Stopping $prog: " /usr/bin/killall /usr/local/bin/stunnel >/dev/null 2>&1 RETVAL=$? [ $RETVAL -eq 0 ] && success [ $RETVAL -ne 0 ] && failure echo return $RETVAL } restart() { stop start } case "$1" in start) start ;; stop) stop ;; restart) restart ;; *) echo $"Usage: $0 {start|stop|status|restart}" exit 1 esac 


You can run and check, for example,
request to server address:
 # curl http://10.1.5.133:8080/ext-bus-nsi-service/services/Nsi?wsdl <?xml version='1.0' encoding='UTF-8'?><wsdl:definitions xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://dom.gosuslugi.ru/schema/integration/8.7.0.7/nsi-service/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:nsi-common="http://dom.gosuslugi.ru/schema/integration/8.7.0.7/nsi-common/" xmlns:nsi="http://dom.gosuslugi.ru/schema/integration/8.7.0.7/nsi/" xmlns:ns="http://www.w3.org/2000/09/xmldsig#" xmlns:base="http://dom.gosuslugi.ru/schema/integration/8.7.0.7/" targetNamespace="http://dom.gosuslugi.ru/schema/integration/8.7.0.7/nsi-service/"> <wsdl:types> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://dom.gosuslugi.ru/schema/integration/8.7.0.7/nsi-service/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:nsi-common="http://dom.gosuslugi.ru/schema/integration/8.7.0.7/nsi-common/" xmlns:nsi="http://dom.gosuslugi.ru/schema/integration/8.7.0.7/nsi/" xmlns:ns="http://www.w3.org/2000/09/xmldsig#" xmlns:base="http://dom.gosuslugi.ru/schema/integration/8.7.0.7/"> <xs:import namespace="http://dom.gosuslugi.ru/schema/integration/8.7.0.7/" schemaLocation="http://54.76.42.99:60046//ext-bus-nsi-service/services/Nsi?xsd=hcs-basetypes-8.7.0.7.xsd"/> <xs:import namespace="http://dom.gosuslugi.ru/schema/integration/8.7.0.7/nsi/" schemaLocation="http://54.76.42.99:60046//ext-bus-nsi-service/services/Nsi?xsd=hcs-nsi-types-8.7.0.7.xsd"/> </xs:schema> ……… 


If the output is completely different, then carefully look into the log file /var/log/stunnel.log .

XMLDSig generation


When the tunnel to the GIS utilities server is up and running, you can send any requests there and receive the necessary answers. The request is sent in the form of XMLDSig, which contains the request itself, the hash of the request, the certificate hash, the certificate itself, the signature of the request hash with the certificate hash and a bunch of fields describing it all. The most difficult thing was to unwind the whole chain and get XML, which is successfully tested by the GIS utilities. All signed blocks are taken in the canonical form, and the resulting signatures and hash sums are encoded in BASE64.

The XMLDSig generation algorithm can be implemented using any convenient programming language. We used python 2.7.11, the demo code is attached. As an example, I will also give the console analog.

0. From the certificate, the serial number and data about the released person are obtained, the necessary id are generated and the current time is remembered.
1. With the help of any soap client (for example, suds in Python) a SOAP request is made to the GIS utilities server.
Example of a SOAP request:
 <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:ns0="http://dom.gosuslugi.ru/schema/integration/8.7.0.3/nsi/" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Header> <RequestHeader xmlns="http://dom.gosuslugi.ru/schema/integration/8.7.0.4/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Date>2016-04-11T14:28:28</Date> <MessageGUID>29f93de1-25b6-21e5-24ae-2c6f65dfe2b2</MessageGUID> <SenderID>4eb0a7d6-6317-45cf-8974-10e75cbb0cbc</SenderID> </RequestHeader> </SOAP-ENV:Header> <ns1:Body> <ns0:exportDataProviderNsiItemRequest Id="signed-element"> <ns0:RegistryNumber>51</ns0:RegistryNumber></ns0:exportDataProviderNsiItemRequest> </ns1:Body> </SOAP-ENV:Envelope> 

Where SenderID is the identifier of the management company on whose behalf the request is being made. MessageGUID is a unique ID generated as desired. The body <ns1:Body> is the request itself with additional fields. Id="signed-element" - ID of the request, which we indicate, how we want and which we are oriented to, signing the request.


2. Take the contents of the <ns1:Body> (more precisely, the part with Id and without the first and last newline character), is canonized by the C14N algorithm (exclusive = True), the hash sum is calculated from it according to GOST and output as BASE64. We digest1 . Console analog:
 # cat in.xml ; echo <ns0:exportDataProviderNsiItemRequest Id="signed-element"> <ns0:RegistryNumber>51</ns0:RegistryNumber></ns0:exportDataProviderNsiItemRequest> # cat in.xml | openssl dgst -engine gost -md_gost94 -binary | base64 

* openssl with GOST support. The engine is explicitly specified, but by setting up openssl.cnf, you can not do this.

3. The certificate is taken in x509, decoded from BASE64, the hash sum is considered from it, and the output is encoded in BASE64. We digest2 .

4. Using the data obtained, the content of the <xades:SignedProperties> (see the Template) is formed, canonized by the C14N algorithm (exclusive = False) and is considered digest3 (BASE64) from the content.

5. The <ds:SignedInfo> block is formed, canonized by the C14N algorithm (exclusive = False), signed and encoded in BASE64. The value of the <ds:SignatureValue> block is obtained. Console analog:
 cat SignedInfo.xml | openssl dgst -sign private.key -engine gost -md_gost94 -binary | base64 

where SignedInfo.xml is an already canonized block, without the last line feed.

6. All values ​​obtained are entered into
XAdES-BES XML Document Template:
 <ds:Signature Id="xmldsig-{signature_id}" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <ds:SignedInfo> <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411"/> <ds:Reference Id="xmldsig-{signature_id}-ref0" URI="#{signed_id}"> <ds:Transforms> <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/> <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/> </ds:Transforms> <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr3411"/> <ds:DigestValue>{digest1}</ds:DigestValue> </ds:Reference> <ds:Reference Type="http://uri.etsi.org/01903#SignedProperties" URI="#xmldsig-{signature_id}-signedprops"> <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr3411"/> <ds:DigestValue>{digest3}</ds:DigestValue> </ds:Reference> </ds:SignedInfo> <ds:SignatureValue Id="xmldsig-{signature_id}-sigvalue"> {signature_value} </ds:SignatureValue> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <ds:X509Data> <ds:X509Certificate> {x590_cert} </ds:X509Certificate> </ds:X509Data> </ds:KeyInfo> <ds:Object><xades:QualifyingProperties Target="#xmldsig-{signature_id}" xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" xmlns:xades141="http://uri.etsi.org/01903/v1.4.1#"><xades:SignedProperties Id="xmldsig-{signature_id}-signedprops"><xades:SignedSignatureProperties><xades:SigningTime>{signing_time}</xades:SigningTime><xades:SigningCertificate><xades:Cert><xades:CertDigest><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr3411"/><ds:DigestValue>{digest2}</ds:DigestValue></xades:CertDigest><xades:IssuerSerial><ds:X509IssuerName>{x509_issuer_name}</ds:X509IssuerName><ds:X509SerialNumber>{x509_sn}</ds:X509SerialNumber></xades:IssuerSerial></xades:Cert></xades:SigningCertificate></xades:SignedSignatureProperties></xades:SignedProperties></xades:QualifyingProperties></ds:Object> </ds:Signature> 


It was possible to form this template by picking and analyzing the complex , which recommends the use of GIS utilities. He himself is in the public domain, but requires a cryptographic cryptographic information system CryptoPro CSP and a cryptographic network Trusted Java 2.0 to work.

7. In order to verify the signature thus formed, it is necessary to do all the steps in the reverse order. Console signature verification option:
 # cat SignedInfo.xml | openssl dgst -engine gost -md_gost94 -verify <(openssl x509 -engine gost -in public.pem -pubkey -noout) -signature signature.sig 

where signature.sig is the signature decoded from BASE64, and the whole signed <ds:SignedInfo>...</ds:SignedInfo> block of the checked block. The values ​​of the hash sums are simply compared.

You can take and use the python demo code (at your own risk) from here . The author of the code is Vyacheslav (@slavam, RO). Such an algorithm can be implemented using any convenient language, without the need to purchase any additional tools and components. The call to the OpenSSL utilities from the code looks clumsy, but it works both on Linux and on the Windows platform and makes it possible to refuse to use CryptoPro and additional components.
The system of interaction with the GIS utilities is still at the stage of creation, but the resulting XMLDSig passes the necessary checks.
We hope this article will make it easier for someone to implement such a bike.

UPD
In order to interact with the GIS utilities changes occur. The current rules can be found on the website . Major changes:

It also appeared quite decent software examples that (if you try) you can make friends with OpenSSL.

UPD2
To get OpenSSL under windows with GOST support you need:
1. Put OpenSSL from here , it is already with GOST support, in the lib folder there is a corresponding library: gost.lib .

2. In the configuration file, explicitly indicate its use:
 openssl_conf = openssl_def [openssl_def] engines = engine_section [engine_section] gost = gost_section [gost_section] engine_id = gost dynamic_path = ./gost.dll default_algorithms = ALL 

To determine exactly where OpenSSL is reading the configuration, you can set the environment variable:
 set OPENSSL_CONF=C:\OpenSSL-Win32\openssl.cnf 


3. Check the result:
 C:\OpenSSL-Win32\bin>openssl.exe ciphers -v | find /I "gost" GOST2001-GOST89-GOST89 SSLv3 Kx=GOST Au=GOST01 Enc=GOST89(256) Mac=GOST89 GOST94-GOST89-GOST89 SSLv3 Kx=GOST Au=GOST94 Enc=GOST89(256) Mac=GOST89 


Thanks for attention.

Links


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


All Articles