В прошлом посте я рассказывал про замечательный облачный сервис RabbitMQ as a Service.
Тем кто будет работать с rabbitmq из PHP, стоит детально изучить мануал с сайта (перевод на русский, только код немного устарел), единственная проблема не учтенная в мануале — это безопасность.
Если мы передаем данные через открытый канал связи, или пользуемся публичными ресурсами, а в нашем случае это так, тот кто владеет сервисом или потенциально получит доступ к учетной записи службы — сможет управлять всем тем, что вы накрутили на данном сервисе, но уже на нашей стороне.
Как нам защититься?
Решать проблему безопасности, мы будем путем добавления электронной подписи в сообщение при помощи openssl.
Дальше пойдет немного «индусского», но вполне рабочего кода.
1. Создаем закрытый ключ и сертификат:
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout privateKey.key -out certificate.crt \ -subj '/C=RU/ST=Moscow/L=Moscow/O=IT/OU=IT Departament/CN=test.ru/emailAddress=test@test.ru' openssl pkcs12 -export -out cert.p12 -in certificate.crt -inkey privateKey.key |
На выходе получим 3 файла:
certificate.crt — Публичный сертификат;
privateKey.key — Приватный ключ;
cert.p12 — Файл, содержащий ключевую пару (закрытый ключ и сертификат).
2. Для работы с rabbitmq нам понадобиться composer расширение php-amqplib/php-amqplib:
composer.json
{ "require": { "php-amqplib/php-amqplib": ">=2.6.1" } } |
3. Код сендера сообщений:
sender.php
<?php require('vendor/autoload.php'); #define('AMQP_DEBUG', true); use PhpAmqpLib\Connection\AMQPStreamConnection; use PhpAmqpLib\Message\AMQPMessage; //Подключаемся к rabbitmq $url = parse_url('amqp://cecmqwhe:PASSWORD@puma.rmq.cloudamqp.com/cecmqwhe'); $connection = new AMQPStreamConnection($url['host'], 5672, $url['user'], $url['pass'], substr($url['path'], 1)); $channel = $connection->channel(); $channel->queue_declare('hello', false, false, false, false); if (!$data = file_get_contents("cert.p12")) { echo "Error: Unable to read the cert file\n"; exit; } //Открываю файл с ключевой парой и извлекаю сертификат и закрытый ключ. "123456" пароль указанный при конвертации файла cert.p12 openssl_pkcs12_read($data, $container,'123456'); $privatekey = openssl_get_privatekey($container['pkey']); $pubkey = openssl_pkey_get_public($container['cert']); $str='test'; //подписываю текст openssl_sign($str, $signature, $privatekey); // Освобождаем память от закрытого ключа openssl_free_key($privatekey); // Мы будем передавать в rabbit сериализованный массив (т.е. представленное в виде строки) // Так как подпись содержит бинарные данные, мы также её преобразуем через base64_encode $text = serialize(array('str'=>$str,'signature'=>base64_encode($signature))); echo $text; $msg = new AMQPMessage($text); $channel->basic_publish($msg, '', 'hello'); echo " [x] Sent 'Hello World!'\n"; $channel->close(); $connection->close(); ?> |
4. Код ресивера:
receive.php
<?php require_once __DIR__ . '/vendor/autoload.php'; use PhpAmqpLib\Connection\AMQPStreamConnection; $url = parse_url('amqp://cecmqwhe:PASSWORD@puma.rmq.cloudamqp.com/cecmqwhe'); $connection = new AMQPStreamConnection($url['host'], 5672, $url['user'], $url['pass'], substr($url['path'], 1)); // Для работы нам не нужна ключевая пара, только открытый сертификат: if (!$data = file_get_contents("certificate.crt")) { echo "Error: Unable to read the cert file\n"; exit; } $cert=file_get_contents('certificate.crt'); $pubkey = openssl_pkey_get_public($cert); $channel = $connection->channel(); $channel->queue_declare('hello', false, false, false, false); echo ' [*] Waiting for messages. To exit press CTRL+C', "\n"; $callback = function($msg) { global $pubkey; // Проводим десериализацию $data = unserialize($msg->body); echo " [x] Received ", $data['str'], "\n"; //проверяю ЭЦП $ok = openssl_verify($data['str'],base64_decode($data['signature']), $pubkey); if ($ok == 1) { echo "ЭЦП корректна :)"; } else { echo "ЭЦП не корректна!"; } }; $channel->basic_consume('hello', '', false, true, false, false, $callback); while(count($channel->callbacks)) { $channel->wait(); } $channel->close(); $connection->close(); ?> |
5. Проверка кода:
# php sender.php a:2:{s:3:"str";s:4:"test";s:9:"signature";s:344:"Zi7NGKOU....<cut>==";} [x] Sent 'Hello World!' # php receive.php [*] Waiting for messages. To exit press CTRL+C [x] Received test ЭЦП корректна :) |
Код далеко не идеален, и не рассматривает большинство угроз, которые могут возникнуть в ходе эксплуатации, но по крайней мере позволит однозначно защитить сообщение от подделки.