Pull to refresh

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

Reading time 8 min
Views 23K
С криптографией я сталкивался ранее, приходилось разворачивать удостоверяющий центр на КриптоПро в свое время, так что общие представления о том что такое закрытые и открытые ключи и сертификаты у меня имелось, но вот о том как все это работает в 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-соединений вплоть до поднятия своего веб сервера с авторизацией по сертификатам с алгоритмами ГОСТ.
Tags:
Hubs:
+26
Comments 8
Comments Comments 8

Articles