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

от автора

С криптографией я сталкивался ранее, приходилось разворачивать удостоверяющий центр на КриптоПро в свое время, так что общие представления о том что такое закрытые и открытые ключи и сертификаты у меня имелось, но вот о том как все это работает в 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 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-соединений вплоть до поднятия своего веб сервера с авторизацией по сертификатам с алгоритмами ГОСТ.

ссылка на оригинал статьи http://habrahabr.ru/post/189352/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *