...

среда, 7 августа 2013 г.

[Из песочницы] Взаимодействие 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-соединений вплоть до поднятия своего веб сервера с авторизацией по сертификатам с алгоритмами ГОСТ.

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends: 'You Say What You Like, Because They Like What You Say' - http://www.medialens.org/index.php/alerts/alert-archive/alerts-2013/731-you-say-what-you-like-because-they-like-what-you-say.html


Комментариев нет:

Отправить комментарий