Back to Home

Php-soap interaction on linux with certificate authorization using GOST algorithms

php · soap · openssl · gost

Php-soap interaction on linux with certificate authorization using GOST algorithms

I came across cryptography earlier, I had to deploy a certification center on CryptoPro at one time, so I had general ideas about what private and public keys and certificates were, but there wasn’t much idea of ​​how all this worked in Linux.
The task arose to ensure interaction with RosMinzdrav’s services, namely, with the federal registry of medical workers using the SOAP protocol. On the client side, the system is on CentOS and running services in PHP, on the server side there is a SOAP service with certificate authorization using GOST algorithms. There was a flash drive with a private key formed by the certification center of RosMinzdrav and a certificate of this key.
After analyzing the situation regarding the use of GOST encryption algorithms in the Linux world, it was found that in recent years there has been a good movement forward, but still, not all is well. So, in order to make the php-soap extension transparently understand GOST algorithms, as well as use certificates and keys issued by RosMinzdrav, you need to do the following:

1. Update the OpenSSL library in the distribution package to version no lower than 1.0.1s and configure GOST support.
2. Convert the issued key and certificate to a format that OpenSSL understands. Check the operation of OpenSSL.
3. Fix OpenSSL extension in PHP and recompile PHP itself. Test SOAP in PHP.

So let's get started. Data of the distribution kit installed by php and the openssl library:

# 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 Library Update

The method of compiling "from source" and clogging up the system is not our method. Therefore, the best option is to find the finished package, and I found it in the axivo repository:
# rpm -ivh --nosignature http://rpm.axivo.com/redhat/axivo-release-6-1.noarch.rpm
# yum --enablerepo=axivo update openssl

Checking the installed version of OpenSSL:
# openssl version
OpenSSL 1.0.1e 11 Feb 2013

Next, you need to configure the library to use GOST algorithms, for this we edit the settings file that lies at the address /etc/pki/tls/openssl.cnf, adding the line to the very beginning of the file:
openssl_conf = openssl_def

and at the very end of the file:
[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

After the changes are made, we check whether OpenSSL sees the GOST algorithms:
#openssl ciphers | tr ":" "\n" | grep GOST
GOST2001-GOST89-GOST89
GOST94-GOST89-GOST89

Convert the issued key and certificate to a format that OpenSSL understands

For this we need a Windows machine with CryptoPRO CSP 3.6 installed. I leaked it from the developer's site (it gives a demo mode for 3 months).
The first thing we do is export the key container to the registry using CryptoPro. To do this, open CryptoPro from the Windows control panel. We insert our key medium into the computer. For a regular flash drive, we make sure that among the readers on the “Equipment” tab we have “All removable drives” and “Registry”. If you do not have a regular flash drive but something like RuToken or Etoken or other media, you must have its drivers and library for use in CryptoPro (It should be visible in the "Configure Readers" section on the same tab).

Next, go to the “Service” tab and click “Copy”, click “Browse” and select your key container from the list. If we suddenly don’t find him there, then we return to the previous paragraph and check everything. Next, enter the meaningful name of the new key container, click "Finish" and select "Registry" as the medium in the window that appears. We do not set a new password on the container.
Now we need to install the certificate in a new container. On the “Service” tab in CryptoPro, click the “Install Personal Certificate” button, indicate the file with our certificate, click “Next”, select the container that we just created and do not forget the “Install Certificate in the Container” checkbox.
In order to export this certificate in PKSC # 12 format along with the private key, we need a third-party utility. Which you can either buy here or find on the Internet by the name of p12fromgostcsp. We launch this utility, select our certificate, leave the password empty and save it in the mycert.p12 file.
Copy the received certificate to the linux machine. We check that in the certificate we have both keys - private and public:
# openssl pkcs12 -in mycert.p12 -nodes

When prompted for a password, just press Enter. And look for the presence of the BEGIN CERTIFICATE and BEGIN PRIVATE KEY strings. If there are lines, then everything is in order. Convert the received certificate to the PEM format:
# openssl pkcs12 -in mycert.p12 -out mycert.pem -nodes -clcerts

If you need not only authorization by a client certificate, but also a check of the validity of the server itself, you will need the root certificate of the certification authority. It can be simply opened via Windows and saved in DER format in X.509 encoding (From the “Composition” tab when viewing a certificate), since it does not contain a private key. Then convert it to PEM format via OpenSSL.
# openssl x509 -inform DER -in cacert.cer -outform PEM -out cacert.pem

Where cacert.cer is the name of the source file with the root certificate. You can check the connection to the server using certificates through the OpenSSL command:
# openssl s_client -connect service.rosminzdrav.ru:443 -CAfile cacert.pem -cert mycert.pem

As a result, if everything is done correctly, you should get this output at the end:
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)

Editing and recompiling PHP

The main problem is that in order for OpenSSL to use the default configuration file (this is where we have the settings for the GOST algorithms), you must call the OPENSSL_config (NULL) function before using it. This has not been done in the PHP OpenSSL extension, therefore the PHP-SOAP module does not see GOST algorithms when using an SSL connection. By the way, the same applies to other libraries, for example curl. It also needs to be patched if you are going to work with it.
So let's get started. In order for us to correct OpenSSL, we need to recompile all PHP, since in CentOS the OpenSSL extension was not made by a module.
We prepare the environment for building packages:
# 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}

Download the PHP sources and install them:
# 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

Go to the SOURCES folder and extract php, make a copy of the folder with the sources:
# cd SOURCES
# tar xvjf php-5.3.3.tar.bz2
# cp php-5.3.3 php-5.3.3p -R

Edit the file php-5.3.3p/ext/openssl/openssl.c:
Find the line SSL_library_init();(I have it line number 985) and write in front of it
OPENSSL_config(NULL);.
Go to the SOURCES folder and form a patch:
# diff -uNr php-5.3.3/ php-5.3.3p/ > php-5.3.3-gostfix.patch

The result is a file with the following contents:
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();

Now we need to force the collector to use our patch. Assembly rules are described in the SPEC / php.spec file. We open it and after the patch description line (I have this line starting with Patch230), insert the line with the description of the new patch:
Patch231: php-5.3.3-gostfix.patch

We also prescribe the call of this patch before assembly, for this we look for lines starting with% patch and at the end of them we prescribe
%patch231 -p1

Save the file. Before assembling the package, put all the dependencies for the assembly:
# 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

Next, proceed to the assembly, from the rpmbuld folder, run the command:
rpmbuild -ba SPECS/php.spec

The assembly takes a long time, and if everything went well, ready rpm_packages will appear in the RPMS / {Your_architecture} folder.
For x86_64 architecture:
# 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

Now we can install all this “good” on top of already installed php with a replacement:
rpm -Uvh --replacepkgs --replacefiles RPMS/x86_64/*

Next, check the operation of php-soap. For example, you can use the following code (example for the service "Federal Register of Healthcare Providers"):
 $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 "

Exception Error!

"; echo $sp->__getLastRequest(); echo get_class($e); echo $e->getMessage(); }

If everything went well, no errors will occur and PHP-SOAP will work without problems.
Further, if necessary, you can also tweak the curl library, check the server certificate and, in general, all operations that are valid for SSL connections up to raising your web server with authorization according to certificates with GOST algorithms.

Read Next