Пользователь
0,0
рейтинг
7 августа 2013 в 20:49

Разработка → Взаимодействие php-soap на linux с авторизацией по сертификатам с использованием алгоритмов ГОСТ из песочницы tutorial

С криптографией я сталкивался ранее, приходилось разворачивать удостоверяющий центр на КриптоПро в свое время, так что общие представления о том что такое закрытые и открытые ключи и сертификаты у меня имелось, но вот о том как все это работает в Linux представления особого не было.
Встала задача обеспечить взаимодействие со службами РосМинздрава, а именно — с федеральным регистром медработников по протоколу SOAP. Со стороны клиента система на CentOS и работающими службами на PHP, со стороны сервера — SOAP-сервис с авторизацией по сертификатам с использованием ГОСТ алгоритмов. В наличии была флешка с закрытым ключом, сформированным удостоверяющим центром РосМинздрава и сертификат этого ключа.
После анализа ситуации по использованию ГОСТ алгоритмов шифрования в мире Linux выяснено что за последние годы здесь есть хорошее движение вперед, но все таки не совсем все хорошо. Итак, для того чтобы заставить расширение php-soap прозрачно понимать алгоритмы ГОСТ, а также использовать сертификаты и ключи выданные РосМинздравом нужно сделать следующее:

1. Обновить в дистрибутиве библиотеку OpenSSL до версии не ниже 1.0.1с и настроить поддержку ГОСТ.
2. Преобразовать выданный ключ и сертификат в формат, понятный OpenSSL. Проверить работу OpenSSL.
3. Подправить расширение OpenSSL в PHP и перекомпилировать сам PHP. Протестировать работу SOAP на PHP.

Итак, приступим. Данные дистрибутива, установленного php и библиотеки openssl:

# lsb_release -a
LSB Version:	:base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-noarch
Distributor ID:	CentOS
Description:	CentOS release 6.4 (Final)
Release:	6.4
Codename:	Final

# yum list installed | grep php
php.x86_64             5.3.3-22.el6     @base                                   
php-cli.x86_64         5.3.3-22.el6     @base                                   
php-common.x86_64      5.3.3-22.el6     @base                                   
php-dba.x86_64         5.3.3-22.el6     @base                                   
php-devel.x86_64       5.3.3-22.el6     @base                                   
php-imap.x86_64        5.3.3-22.el6     @base                                   
php-ldap.x86_64        5.3.3-22.el6     @base                                   
php-lessphp.noarch     0.3.9-1.el6      @epel                                   
php-mbstring.x86_64    5.3.3-22.el6     @base                                   
php-mcrypt.x86_64      5.3.3-1.el6      @epel                                   
php-odbc.x86_64        5.3.3-22.el6     @base                                   
php-pdo.x86_64         5.3.3-22.el6     @base                                   
php-pear.noarch        1:1.9.4-4.el6    @base                                   
php-pgsql.x86_64       5.3.3-22.el6     @base                                   
php-process.x86_64     5.3.3-22.el6     @base                                   
php-shout.x86_64       0.9.2-6.el6      @epel                                   
php-soap.x86_64        5.3.3-22.el6     @base                                   
php-xml.x86_64         5.3.3-22.el6     @base                                   
php-xmlrpc.x86_64      5.3.3-22.el6     @base 

# yum list installed | grep openssl
openssl.x86_64         1.0.0-27.el6_4.2 @updates                                
openssl-devel.x86_64   1.0.0-27.el6_4.2 @updates  

Обновление библиотеки OpenSSL

Метод собирать «из исходников» и засорять систему — не наш метод. Поэтому лучший вариант — найти готовый пакет, и я его нашел в репозитории axivo:
# rpm -ivh --nosignature http://rpm.axivo.com/redhat/axivo-release-6-1.noarch.rpm
# yum --enablerepo=axivo update openssl

Проверяем установленную версию OpenSSL:
# openssl version
OpenSSL 1.0.1e 11 Feb 2013

Далее необходимо настроить библиотеку на использование алгоритмов ГОСТ, для этого редактируем файл настроек который лежит по адресу /etc/pki/tls/openssl.cnf, добавив в самое начало файла строку:
openssl_conf = openssl_def

и в самый конец файла:
[openssl_def]
engines=engine_section

[engine_section]
gost=gost_section

[gost_section]
engine_id=gost
#ВНИМАНИЕ!! ПУТЬ ЗАВИСИТ ОТ ДИСТРИБУТИВА
dynamic_path=/usr/lib64/openssl/engines/libgost.so
default_algorithms=ALL
CRYPT_PARAMS=id-Gost28147-89-CryptoPro-A-ParamSet

После внесенных изменений проверяем, видит ли OpenSSL алгоритмы ГОСТ:
#openssl ciphers | tr ":" "\n" | grep GOST
GOST2001-GOST89-GOST89
GOST94-GOST89-GOST89

Преобразуем выданный ключ и сертификат в формат, понятный OpenSSL

Для этого нам понадобится Windows машина с установленным CryptoPRO CSP 3.6. Я его слил с сайта разработчика (там дается демонстрационный режим режим на 3 месяца).
Первое что делаем — экспортируем ключевой контейнер в реестр средствами КриптоПро. Для этого открываем КриптоПро из панели управления Windows. Вставляем наш ключевой носитель в компьютер. Для обычной флешки убеждаемся, что у нас среди считывателей на вкладке «Оборудование» есть «Все съемные диски» и «Реестр». Если у вас не обычная флешка а что-то типа РуТокен либо Etoken либо другой носитель — необходимо наличие его драйверов и библиотеки для использования в КриптоПро (Его должно быть видно в разделе «Настроить Считыватели» на той же вкладке).

Далее переходим на вкладку «Сервис» и нажимаем «Копировать», нажимаем «Обзор» и выбираем среди списка свой ключевой контейнер. Если его там вдруг не находим — то возвращаемся к предыдущему параграфу и все проверяем. Далее вводим осмысленное имя нового ключевого контейнера, нажимаем «Готово» и в качестве носителя в появившемся окне выбираем «Реестр». Новый пароль на контейнер не ставим.
Теперь нам нужно установить сертификат в новый контейнер. На вкладке «Сервис» в КриптоПро нажимаем кнопку «Установить личный Сертификат», указываем файл с нашим сертификатом, нажимаем «Далее», выбираем только что созданный нами контейнер и не забываем галочку «Установить сертификат в контейнер».
Для того чтобы экспортировать данный сертификат в формате PKSC#12 вместе с закрытым ключом — нам понадобится сторонняя утилита. Которую можно либо купить здесь либо найти на просторах интернета по имени p12fromgostcsp. Запускаем данную утилиту, выбираем наш сертификат, пароль оставляем пустым и сохраняем его в файл mycert.p12.
Копируем полученный сертификат на linux-машину. Проверяем, что в сертификате у нас есть оба ключа — закрытый и открытый:
# openssl pkcs12 -in mycert.p12 -nodes

На запрос пароля просто нажимаем Enter. И смотрим наличие строк BEGIN CERTIFICATE и BEGIN PRIVATE KEY. Если строки есть то все в порядке. Преобразуем полученный сертификат в формат PEM:
# openssl pkcs12 -in mycert.p12 -out mycert.pem -nodes -clcerts

Если вам нужна не только авторизация по клиентскому сертификату, но и также проверка валидности самого сервера — вам понадобится корневой сертификат удостоверяющего центра. Его можно просто через Windows открыть и сохранить в формате DER в кодировке X.509 (Со вкладки «Состав» при просмотре сертификата), так как закрытого ключа он не содержит. Затем преобразовать его в PEM формат через OpenSSL.
# openssl x509 -inform DER -in cacert.cer -outform PEM -out cacert.pem

Где cacert.cer — имя исходного файла с корневым сертификатом. Проверить коннект с сервером с использованием сертификатов можно через OpenSSL командой:
# openssl s_client -connect service.rosminzdrav.ru:443 -CAfile cacert.pem -cert mycert.pem

В итоге, если все сделано верно, у вас должен получится вот такой вывод в конце:
New, TLSv1/SSLv3, Cipher is GOST2001-GOST89-GOST89
Server public key is 256 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : GOST2001-GOST89-GOST89
    Session-ID: ***
    Session-ID-ctx: 
    Master-Key: ***
    Key-Arg   : None
    Krb5 Principal: None
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1375875984
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)

Правка и перекомпиляция PHP

Основная проблема заключается в том, что для того чтобы OpenSSL использовала файл конфигурации по умолчанию (именно там у нас прописаны настройки для алгоритмов ГОСТ) перед ее использованием необходимо вызвать функцию OPENSSL_config(NULL). В расширении PHP OpenSSL этого не сделано, поэтому модуль PHP-SOAP при использовании SSL-соединения не видит алгоритмов ГОСТ. Кстати, тоже самое касается и других библиотек, например curl. Ее тоже нужно патчить если вы собираетесь с ней работать.
Итак приступим. Для того чтобы нам поправить OpenSSL необходимо перекомпилировать весь PHP, так как в CentOS расширение OpenSSL сделано не модулем.
Подготавливаем среду для сборки пакетов:
# yum install rpm-build redhat-rpm-config
# mkdir /root/rpmbuild
# cd /root/rpmbuild
# mkdir BUILD RPMS SOURCES SPECS SRPMS
# mkdir RPMS/{i386,i486,i586,i686,noarch,athlon}

Качаем исходники PHP и устанавливаем их:
# wget http://vault.centos.org/6.4/os/Source/SPackages/php-5.3.3-22.el6.src.rpm
# rpm -ivh php-5.3.3-22.el6.src.rpm

Переходим в папку SOURCES и извлекаем php, делаем копию папки с иходниками:
# cd SOURCES
# tar xvjf php-5.3.3.tar.bz2
# cp php-5.3.3 php-5.3.3p -R

Правим файл php-5.3.3p/ext/openssl/openssl.c:
Находим строку SSL_library_init(); (у меня это строка № 985) и прописываем перед ней
OPENSSL_config(NULL);.
Переходим в папку SOURCES и формируем патч:
# diff -uNr php-5.3.3/ php-5.3.3p/ > php-5.3.3-gostfix.patch

В результате получится файл со следующим содержимым:
diff -uNr php-5.3.3/ext/openssl/openssl.c php-5.3.3p/ext/openssl/openssl.c
--- php-5.3.3/ext/openssl/openssl.c	2010-06-26 20:03:39.000000000 +0400
+++ php-5.3.3p/ext/openssl/openssl.c	2013-08-07 11:32:41.944581280 +0400
@@ -981,7 +981,7 @@
 	le_key = zend_register_list_destructors_ex(php_pkey_free, NULL, "OpenSSL key", module_number);
 	le_x509 = zend_register_list_destructors_ex(php_x509_free, NULL, "OpenSSL X.509", module_number);
 	le_csr = zend_register_list_destructors_ex(php_csr_free, NULL, "OpenSSL X.509 CSR", module_number);
-
+	OPENSSL_config(NULL);
 	SSL_library_init();
 	OpenSSL_add_all_ciphers();
 	OpenSSL_add_all_digests();

Теперь нужно заставить сборщик использовать наш патч. Правила сборки описаны в файле SPEC/php.spec. Открываем его и после строки описания патчей (у меня это строка начинающаяся на Patch230) вставляем строку с описанием нового патча:
Patch231: php-5.3.3-gostfix.patch

Также прописываем вызов данного патча перед сборкой, для этого ищем строки начинающиеся на %patch и в конце них прописываем
%patch231 -p1

Сохраняем файл. Перед сборкой пакета ставим все зависимости для сборки:
# yum install bzip2-devel db4-devel gmp-devel httpd-devel pam-devel sqlite-devel pcre-devel libedit-devel libtool-ltdl-devel libc-client-devel cyrus-sasl-devel openldap-devel mysql-devel postgresql-devel libxml2-devel net-snmp-devel libxslt-devel libxml2-devel libXpm-devel libpng-devel freetype-devel libtidy-devel aspell-devel recode-devel libicu-devel enchant-devel net-snmp

Далее приступаем к сборке, из папки rpmbuld запускаем команду:
rpmbuild -ba SPECS/php.spec

Сборка занимает продолжительное время, и в случае если все прошло хорошо, в папке RPMS/{Ваша_архитектура} появятся готовые rpm_пакеты.
Для архитектуры x86_64:
# ls RPMS/x86_64/
php-5.3.3-22.el6.x86_64.rpm            
php-devel-5.3.3-22.el6.x86_64.rpm     
php-intl-5.3.3-22.el6.x86_64.rpm      
php-pgsql-5.3.3-22.el6.x86_64.rpm    
php-tidy-5.3.3-22.el6.x86_64.rpm
php-bcmath-5.3.3-22.el6.x86_64.rpm     
php-embedded-5.3.3-22.el6.x86_64.rpm  
php-ldap-5.3.3-22.el6.x86_64.rpm      
php-process-5.3.3-22.el6.x86_64.rpm  
php-xml-5.3.3-22.el6.x86_64.rpm
php-cli-5.3.3-22.el6.x86_64.rpm        
php-enchant-5.3.3-22.el6.x86_64.rpm   
php-mbstring-5.3.3-22.el6.x86_64.rpm  
php-pspell-5.3.3-22.el6.x86_64.rpm   p
hp-xmlrpc-5.3.3-22.el6.x86_64.rpm
php-common-5.3.3-22.el6.x86_64.rpm     
php-fpm-5.3.3-22.el6.x86_64.rpm       
php-mysql-5.3.3-22.el6.x86_64.rpm     
php-recode-5.3.3-22.el6.x86_64.rpm   
php-zts-5.3.3-22.el6.x86_64.rpm
php-dba-5.3.3-22.el6.x86_64.rpm        
php-gd-5.3.3-22.el6.x86_64.rpm        
php-odbc-5.3.3-22.el6.x86_64.rpm     
php-snmp-5.3.3-22.el6.x86_64.rpm
php-debuginfo-5.3.3-22.el6.x86_64.rpm  
php-imap-5.3.3-22.el6.x86_64.rpm      
php-pdo-5.3.3-22.el6.x86_64.rpm       
php-soap-5.3.3-22.el6.x86_64.rpm

Теперь можем установить все это «добро» поверх уже установленного php с заменой:
rpm -Uvh --replacepkgs --replacefiles RPMS/x86_64/*

Далее проверяем работу php-soap. Для примера можно использовать такой код (пример для сервиса «Федеральный регистр медработников»):
<?php
class GetEmployees
{
public $ogrn;
}
    $cert="/mycert.pem";
 //Сертификат
    $wsdl="https://service.rosminzdrav.ru/MedStaffIntegration/MedStaff.svc?wsdl"; //Адрес wdsl сервиса
    $loc = "https://service.rosminzdrav.ru/MedStaffIntegration/medstaff.svc/basic";
 //Адрес точки доступа
    $sp = new SoapClient($wsdl,array(
            'local_cert' => $cert,
             'trace' => 1,
            'exceptions' => 1,
            'soap_version' => SOAP_1_1,
            'location' =>$loc,
            ));
    $emp = new GetEmployees;
    $emp->ogrn = '1022303554570';
    try{
         $data = $sp->GetEmployees($emp);
         print_r($data);
        }  catch (SoapFault $e) { 
        echo "<h2>Exception Error!</h2>"; 
        echo $sp->__getLastRequest();
        echo get_class($e);
        echo $e->getMessage(); 
}

Если все прошло успешно никаких ошибок не возникнет и PHP-SOAP без проблем заработает.
Далее если необходимо можно также подправить библиотеку curl, сделать проверку сертификата сервера и вообще все операции которые допустимы для SSL-соединений вплоть до поднятия своего веб сервера с авторизацией по сертификатам с алгоритмами ГОСТ.
Андрей Григорьев @xtron
карма
8,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

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

  • –1
    Почему-то когда слышу слово ГОСТ, всегда представляется нечто древнее, сильно устаревшее и скорее всего сделанное через то самое место через которое вам пришлось всё это настраивать.
    • –4
      а с PHP нет подобных ассоциаций?
  • 0
    Нашел для себя некоторые интересные моменты, труд имеет смысл. Но вот скажите, зачем вы пишите «Для того чтобы экспортировать данный сертификат в формате PKSC#12 вместе с закрытым ключом — нам понадобится сторонняя утилита. Которую можно либо купить здесь либо найти на просторах интернета по имени p12fromgostcsp.» ??
    Если ключ помечен как экспортируемый, то это действие выполняется через диалог просмотра сертификата -> вкладка «состав» -> Копировать в файл. А если ключ не помечен как экспортируемый, то она ничем не поможет. Бесполезная софтина, на мой взгляд.
    • 0
      Не все так просто как кажется на первый взгляд. На самом деле сертификат, экспортируемый через стандартный диалог просмотра сертификата не распознается openssl. Получается такая ошибка:
      MAC verified OK
      Bag Attributes
          localKeyID: 01 00 00 00 
          friendlyName: REGISTRY\\mstaff
          Microsoft CSP Name: Crypto-Pro GOST R 34.10-2001 Cryptographic Service Provider
      Error outputting keys and certificates
      140017040754368:error:06074079:digital envelope routines:EVP_PBE_CipherInit:unknown pbe algorithm:evp_pbe.c:167:TYPE=1.2.840.113549.1.12.1.80
      140017040754368:error:23077073:PKCS12 routines:PKCS12_pbe_crypt:pkcs12 algor cipherinit error:p12_decr.c:83:
      140017040754368:error:2306A075:PKCS12 routines:PKCS12_item_decrypt_d2i:pkcs12 pbe crypt error:p12_decr.c:130:
      

      Вот как раз эта утилита и позволяет избежать такой ошибки. Цитирую разработчиков данной утилиты:

      Контейнер PKCS#12, создаваемый утилитой P12FromGostCSP полностью совместим с аналогичными контейнерами, создаваемыми ООО «КриптоКом» (в рамках проекта openssl) и ООО «Топ Кросс», чего, к сожалению, не скажешь о контейнере, создаваемом программными средствами, входящими в состав КриптоПро CSP (начиная с версии R3).

      Просмотреть ASN1-структуры контейнера PKCS#12, созданного средствами КриптоПро CSP R3, и контейнеров, созданных другими средствами, удобно утилитами openssl или lirssl следующего вида:

      #openssl asn1parse –inform DER –in <контейнер PKCS#12>

      Если вы сравните эти структуры, то вам сразу бросится в глаза, что, например, вместо алгоритма хэширования ГОСТ Р 34.11-94 в контейнере от КриптоПро используется SHA1. Еще более интересный результат вы получите, если попробуете посмотреть содержимое контейнера, выполнив следующую команду:

      #openssl pkcs12 –in <контейнер PKCS#12>
      • 0
        > вместо алгоритма хэширования ГОСТ Р 34.11-94 в контейнере от КриптоПро используется SHA1

        И это сертифицировано в РФ?
        • 0
          Ну с этими вопросами думаю к разработчикам КриптоПро. Хотя я не настолько знаю Windows CrypoAPI чтобы сказать задействован ли сам крипто-провайдер КриптоПро при экспорте сертификата из контейнера либо это делается полностью средствами Windows.
  • 0
    xtron, огромное спасибо, очень помогли. Плюсы вам везде где можно. Подскажите, а подписывать сконверченным pem ключём получится текстовые сообщения?
    Стоит задача подписывать xml файлы, ну и снимать подписи для тестов.
    • 0
      Да, подписывать возможно любые файлы. Мы подписываем soap-сообщения (по сути тот же XML).

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.