Федеральная система "Город"

В прошлый раз описал процесс работы с платёжной системой Cyberplat, теперь хочу поделиться опытом работы с ФСГ (Федеральная система город).

Разработано сие чудо ЦФТ. Старались делать все по ГОСТ, поэтому произвести интеграцию не так просто, как хотелось бы (рассматриваем PHP).

Издеваясь нал гуглом различными вариациями поисковых запросов и доканывая людей, уже имевших дело с ФСГ, удалось по крупицам собрать всю необходимую информацию для подключения. После чего и появилось желание все это скомпоновать и поделиться с коллегами.

Взаимодействие с системой проиходит по шифрованному ГОСТ алгоритмами каналу через сокеты, посредством передачи подписанных XML сообщений. Плюс zip-архивирование.

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


STunnel

Этот подход достаточно прост.

Как написано выше, шифрование происходит ГОСТ алгоритмами. Сразу всплывает в памяти слово «КриптоПРО». И не зря. И в документации и люди, что занимались подключением, советуют именно этот вариант.

С сайта (https://www.cryptopro.ru/products/other/stunnel) КриптоПРО скачиваем приложение, устанавливаем на сервер по мануалу с сайта. С сервисом для *nix системам всё отлично работает, а под windows не взлетело (что совершенно не критично). Видимо из-за того, что версия под винду с 2013 года не обновлялась.

Всё, канал налажен.


OpenSSL

Этот вариант для любителей позаморачиваться.

В нынешних версиях OpenSSL есть поддержка алгоритмов ГОСТ, но сразу в PHP ничего не заработает (в моём случае PHP 5.6), нужны дополнительные телодвижения. А если точнее, то нужно пересобрать модуль php_openssl.

Скачиваем исходники PHP, в файле ext\openssl\openssl.c находим строчку SSL_library_init(); и вставляем перед ней OPENSSL_config(NULL);.

Как собирать модуль расписывать не буду, мануал есть на php.net. Главное следовать инструкциям и всё получится.

Всё готово, можно открывать сокеты.

Если Вы воспользовались первым способом, то TLS соединение налаживается на стороне STunnel, соответственно остаётся просто открыть сокет.

<?php

$port   = $this->getPort();
$socket = stream_socket_client(host:port),
$errno, $errstr, 60, STREAM_CLIENT_CONNECT);


Если выбрали второй вариант подключения, то всё тоже не на много сложнее (разработка велась на 5.6, но поддержка осуществлялась до 5.4):

<?php

$context = stream_context_create(
   array(
      'ssl' => array(
         'verify_peer' => false,
         'verify_peer_name' => false,
         'allow_self_signed' => true,
         'ciphers' => 'aGOST',
         'disable_compression' => false,
       ),
   )
);
 
$socket = stream_socket_client(host:port,
$errno, $errstr, 60, STREAM_CLIENT_CONNECT, $context);

Сообщения передаются в виде XML-DSIG пакетов. Процесс подписи — это отдельная тема. Самому реализовывать желания никакого не было, поэтому взял найденное на github решение.

Остаётся сформировать XML сообщение, заархивировать и отправить в сокет.

Процесс архивирования у меня реализован через промежуточный файл:

<?php

function zipRequest($request)
{
     $zip  = new \ZipArchive;
     $name = time().'zip';
     $zip->open($name, \ZipArchive::CREATE);
 
     $zip->addFromString('request', $request);
     $zip->close();
     $result = file_get_contents($name);
     unlink($name);
     return $result;
}

Вот и дошли до главного нюанса интеграции.

Сообщение состоит из двух частей. Первая – это размер сообщения, вторая – само сообщение.

Но по спецификации TLS так же отправляет сначала размер сообщения, а потом само сообщение. Это был главный затык в подключении.

<?php

if ($socket) {
   $cnt = pack("N", strlen($data));
   fwrite($socket, $cnt.$data);
 
   $cnt = fread($socket, 4);
   $cnt = unpack("N", $cnt);
   $cnt = $cnt[1];
   if ($cnt < 1024) {
      $result = fread($socket, $cnt);
   } else {
      $ansCnt = 0;
      do {
         $buf = fread($socket, 1024);
         $result .= $buf;
         $ansCnt += strlen($buf);
      } while ($buf != '' || $ansCnt < $cnt);
   }
   fclose($socket);
}


В ответ возвращается похожее заархивированное XML сообщение. Разархивируем и вытаскиваем текст:

<?php

function unzipResponse($response)
{
   $result = '';
   $name   = time().'zip';
   file_put_contents($name, $response);
   $zip    = new \ZipArchive;
   if (($res    = $zip->open($name)) === true) {
      $result = $zip->getFromName('response');
      $zip->close();
   }
   unlink($name);
   return $result;
}


Работа с ФСГ, Киберплатом, Рапидой и А3 реализована тут.



Комментарии

добавить
Комментариев пока нет. Будете первым?
Чтобы комментировать, нужно авторизоваться

Советуем почитать


Почему стоит изучать Ruby on Rails
Администратор 0

Почему стоит изучать Ruby on Rails читать далее

Вы начинающий программист? Или просто думаете какой бы язык изучить? Очень рекомендуем вам обратить внимание на Ruby on Rails. Не смотря на обилие языков программирования и доступных фреймворков, Ruby on Rails очень популярен среди web-разработчиков. Всё благодаря функционалу и скорости разработки.

0 28.01.2018 17:12:42

PHP Управление строками
Максим 0

PHP Управление строками читать далее

Мало кто из разработчиков задумывается о том, как устроено ядро PHP и что происходит «под капотом». Действительно, на практике большинству редко бывают нужны подобные знания, тем не менее обладать ими будет полезно. Статья рассказывает о том, как устроены строки в PHP и о различиях работы с ними в PHP 5 и 7.


Это мой первый перевод подобной статьи, тем более технически не самой простой. Обо всех неточностях пишите в комментариях или лично мне.

0 04.05.2016 23:28:31