Взаимодействие с ГИС ЖКХ с помощью stunnel и openssl по ГОСТу



    Встала перед нами в полный рост задача наладить взаимодействие с ГИС ЖКХ. Согласно документации, предполагается использование небезызвестного отечественного ПО для шифрования туннеля и формирования ЭЦП по ГОСТу, но это не наш метод. Вооружившись гуглом и консолью, я и slavam реализовали необходимый функционал подручными средствами.
    Всё необходимое ПО есть как на Linux, так и на Windows платформах, потому методику можно назвать мультиплатформенной.



    1. Подготовка
    2. Криптотуннель по ГОСТу
    3. Формирование XMLDSig
    4. Ссылки


    Подготовка



    Взаимодействие состоит из следующих частей:
    • Установка по ГОСТу шифрованного туннеля до серверов ГИС ЖКХ.
    • Формирование и отправка подписанного XML запроса.
    • Получение и проверка ответа.

    Для работы используется сервер CentOS 6, Python 2.7.11, OpenSSL + GOST engine, stunnel. Краткая инструкция установки openssl с поддержкой ГОСТ
    для ленивых:
    # 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
    …………
    


    Думаю, ничего сложного быть не должно и всё уже сотни раз описано. А в более свежих дистрибутивах модуль gost есть из коробки.

    OpenSSL используется двояко: с ним собран stunnel, что-бы работал по ГОСТу; и утилита openssl вызывается из кода python-а для формирования и проверки подписей. Вызов openssl обусловлен тем, что не удалось из кода python-на задавать используемый крипто-модуль. Всё необходимое ПО есть как на Linux, так и на Windows платформах, потому методику можно назвать мультиплатформенной.

    Для python-а сервера CentOS 6 нужно будет обновить библиотеку lxml, а для этого поставить несколько дополнительных пакетов:
    # yum install libxml2 libxml2-devel libxslt libxslt-devel python-devel
    # pip install lxml --upgrade
    


    Так же нам потребуются файлы сертификата и закрытого ключа в формате PKCS12 (.pem). Получить их из eToken-а можно с помощью утилит вроде P12FromGostCSP или вручную. Если сделать это по каким-либо причинам не получается, то есть вариант работы с ключом «Рутокен ЭЦП» напрямую. На сайте есть подробные инструкции, как обучить этому OpenSSL и stunnel. Таким образом, задача сводится к предыдущей. У меня под рукой такого ключа не оказалось, потому проверить не удалось.

    Криптотуннель по ГОСТу


    Туннель поднимается с помощью Stunnel, который работает в режиме прокси. Нужно только научить его использовать модуль gost. Но тут есть момент — чтобы модуль инициализировался правильно, нужно слегка исправить исходники. Проблема, как я понял, связана с порядком инициализации модуля и методов. Итак:
    # wget https://www.stunnel.org/downloads/stunnel-5.31.tar.gz
    # tar -xvf stunnel-5.31.tar.gz
    # cd stunnel-5.31
    

    Правим файл src/options.c и в конце функции "NOEXPORT char *engine_init(void)" добавляем вызов SSL_library_init();.
    Получится как-то так:
    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 */
    }
    


    Решение было найдено тут.
    Собираем с подключением ранее собранного OpenSSL:
    # ./configure --with-ssl=/usr/local/ssl  --disable-libwrap
    # make && make install
    


    Файл конфигурации /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
    

    Файлы сертификатов кладём в директорию /etc/crypto/. В общем случае, stunnel может работать под любым пользователем, но у нас пусть будет root.
    Простейший init.d скрипт управления службой:
    #! /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
    


    Можно запускать и проверять, например,
    запросом на адрес сервера:
    # 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>
    ………
    


    Если вывод совсем не похож, то внимательно смотрим в лог файл /var/log/stunnel.log.

    Формирование XMLDSig


    Когда туннель до сервера ГИС ЖКХ настроен и работает, можно слать туда всякие запросы и получать нужные ответы. Запрос отправляется в виде XMLDSig, в котором содержится сам запрос, хеш этого запроса, хеш сертификата, сам сертификат, подпись хеша запроса с хешем сертификата и куча полей, всё это описывающих. Самое сложное как раз было раскрутить всю цепочку и получить XML, который успешно проходит проверку со стороны ГИС ЖКХ. Все подписываемые блоки берутся в каноническом виде, а получаемые подписи и хеш-суммы кодируются в BASE64.

    Алгоритм формирования XMLDSig можно реализовать используя любой удобный язык программирования. Мы использовали python 2.7.11, демонстрационный код прилагается. Как пример, буду так же приводить консольный аналог.

    0. Из сертификата достаются серийный номер и данные о выпустившем, генерируются необходимые id и запоминается текущее время.
    1. С помощью любого soap клиента (например, suds на Python) формируется SOAP-запрос к серверу ГИС ЖКХ.
    Пример SOAP-запроса:
    <?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>
    

    Где SenderID — идентификатор управляющей компании, от лица которой делается запрос. MessageGUID — уникальный ID, генерируемый как пожелается. Тело <ns1:Body> — сам запрос с дополнительными полями. Id="signed-element" — ID запроса, который указываем, как хотим и на который ориентируемся, подписывая запрос.


    2. Берётся содержимое тега <ns1:Body>(точнее часть с Id и без первого и последнего символа перевода строки), канонизируется алгоритмом C14N (exclusive=True), считается от неё хеш-сумма по ГОСТу и выводится в виде BASE64. Получаем digest1. Консольный аналог:
    # 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 с поддержкой ГОСТ. Engine указан явно, но, настроив openssl.cnf, можно этого и не делать.

    3. Берётся сертификат в x509, декодируется из BASE64, считается от него хеш-сумма и вывод кодируется в BASE64. Получаем digest2.

    4. Используя полученные данные, формируется содержимое тега <xades:SignedProperties>(см. Шаблон), канонизируется алгоритмом C14N (exclusive=False) и от содержимого считается digest3 (BASE64).

    5. Формируется блок <ds:SignedInfo>, канонизируется алгоритмом C14N (exclusive=False), подписывается и кодируется в BASE64. Получается значение блока <ds:SignatureValue>. Консольный аналог:
    cat SignedInfo.xml | openssl dgst -sign private.key -engine gost -md_gost94 -binary | base64 
    

    где SignedInfo.xml – уже канонизированный блок, без последнего перевода строки.

    6. Все полученные значения вносятся в
    Шаблон XML-документа формата XAdES-BES:
    <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>
    


    Этот шаблон удалось сформировать расковыряв и проанализировав комплекс, который рекомендует использовать ГИС ЖКХ. Сам он находится в свободном доступе, но для работы требует СКЗИ КриптоПро CSP и СКЗИ Trusted Java 2.0.

    7. Для того, чтобы проверить сформированную таким образом подпись, необходимо проделать все действия в обратном порядке. Консольный вариант проверки подписи:
    # cat SignedInfo.xml | openssl dgst -engine gost -md_gost94 -verify <(openssl x509 -engine gost -in public.pem -pubkey -noout) -signature signature.sig
    

    где signature.sig – раскодированная из BASE64 подпись, а SignedInfo.xml проверяемый блок <ds:SignedInfo>...</ds:SignedInfo> целиком. Значения хеш-сумм просто сравниваются.

    Демонстрационный код на python-е можно взять и использовать (на свой страх и риск) отсюда. Автор кода — Вячеслав (@slavam, RO). Подобный алгоритм можно реализовать средствами любого удобного языка, без необходимости покупки каких-либо дополнительных средств и компонентов. Вызов утилит OpenSSL из кода хоть и выглядит топорным, зато работает как на Linux так и на Windows платформе и позволяет отказаться от использования КриптоПро и дополнительных компонентов.
    Система взаимодействия с ГИС ЖКХ у нас ещё на стадии создания, но получаемый XMLDSig проходит необходимые проверки.
    Надеемся, эта статья облегчит кому-нибудь задачу по реализации подобного велосипеда.

    UPD
    В порядке взаимодействия с ГИС ЖКХ происходят изменения. С актуальными правилами можно ознакомится на сайте. Основные изменения:
    • Теперь для работы с системой необходимо зарегистрировать ИС.
    • Перед тем, как слать запросы, необходимо выполнить basic-авторизацию.
    • Для работы с тестовым стендом, также нужно подать заявку.

    Так же появились вполне приличные программные примеры, которые (если постараться) можно подружить и с OpenSSL.

    UPD2
    Чтобы завести OpenSSL под windows с поддержкой ГОСТ нужно:
    1. Поставить OpenSSL от сюда, он уже с поддержкой GOST, в папке lib есть соответствующая библиотека: gost.lib.

    2. В файле конфигурации явно указать его использование:
    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
    

    Чтобы точно определить, от куда OpenSSL читает конфигурацию, можно выставить переменную окружения:
    set OPENSSL_CONF=C:\OpenSSL-Win32\openssl.cnf
    


    3. Проверить результат:
    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
    


    Спасибо за внимание.

    Cсылки


    Support the author
    Share post

    Similar posts

    Comments 15

      0
      А разве сервисы ГИС ЖКХ доступны где-либо кроме СМЭВ?
      Насколько я помню к самой СМЭВ на бюрократическом уровне подключение ограничено крипто-маршрутизаторами одной компании…
        0
        В методических рекомендациях есть только требования к сертификату, алгоритмам подписи (ГОСТ) и протоколу HTTPS с шифрованием по ГОСТ, для реализации которого предлагают использовать МагПро Криптопакет. Ту же функциональность можно получить способом описанным в статье. Для соединения до серверов ГИС ЖКХ используются общие интернет-каналы.
        Возможно далее и вылезут какие-либо бюрократические грабли, но пока всё тихо.
          0

          Этот уровень описан в списке сертифицированных ФСБ решений. В нём в том числе есть и линукс-версия, доступная бесплатно.

            +1
            В ГИС ЖКХ только кредитные организации подключаются через СМЭВ.
            0
            А в требованиях значится просто использование ГОСТ алгоритмов или же сертифицированных СКЗИ? Можно увидеть выдержку из ТЗ?
              0
              Как такого ТЗ нет. Есть методические рекомендации по работе, ссылка на которые приведена в статье. В рекомендациях не делается акцента на обязательную сертификацию СКЗИ, только на ГОСТ алгоритмы и требования к сертификату. Но там вообще не рассматривается каких-либо вариантов, кроме КриптоПРО :). Судя по тому, что клиенту предлагается реализовать ПО для взаимодействия самим, речь об общей сертификации комплекса не идёт, а требования на ГОСТ мы соблюли.
              +2
              Символ Heartbleed намекает?
                +2
                Намекает на пережитые боль и страдания и грядущую неопределённость.
                0
                Интересная статья, спасибо, надо будет подкинуть разрабам. 13-го мая прошли сертификацию интеграционного взаимодействия с ГИС ЖКХ, теперь можем работать на продакшене спокойно. Ну как спокойно, все неспокойства только начинаются, чую, ох, чую, спокойной эта работа с ними не будет.
                  0
                  Да, у нас тоже будущее туманно, но процесс идёт, а там порешаем. Держите в курсе, если что интересное.
                  0
                  На сколько я помню, 63-ФЗ требует использовать не только «кошерные» алгоритмы, но и СКЗИ, имеющие сертификацию…
                    0
                    openssl входит в состав МагПро КриптоТуннель — сертифицированное СКЗИ, можно его использовать и для подписывания. Если не ошибаюсь, они и сделали поддержку ГОСТ в openssl.
                      0
                      Так сертифицируются то бинарные сборки…
                        0
                        Учитывая рекомендации к взаимодействию, это не грозит.
                          0
                          Ну вот, в этой СКЗИ есть утилита openssl (бинарная сборка) мы ее и используем для вычисления дайджестов и подписания. Иначе каждому оператору придется сертифицировать свою ИС как СКЗИ…

                    Only users with full accounts can post comments. Log in, please.