Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Данная статья написана в соавторстве с тимлидом @Restlin
Выбор кейса и наше видение его решения
Изначально выбор пал на кейс МВД: Разработка автономного программного решения лингвистического анализа и преобразования в тексте лица повествования.
Формулировка кейса:
Учитывая специфику деятельности определенных служб МВД России, при подготовке документов требуется преобразование в тексте лица повествования от первого лица в третье с учетом рода. Например, фраза в исходном тексте «Я увидел, что Иванов пошёл ко мне» в итоговом тексте должна быть преобразована в «Он увидел, что Иванов пошёл к нему». Разработанное программное решение позволит в автоматическом режиме проводить процесс конвертации лица повествования, что позволит сотрудникам уделить больше времени на иные аспекты служебной деятельности. Кейс подготовлен Департаментом информационных технологий, связи и защиты информации МВД России.
Нам он был близок по специализации, и было четкое представление как красиво можно решить данную задачу.
Планировалось, что мы создадим веб-сервис, который умеет автоматически преобразовывать показания свидетеля из первого лица в третье.
Все складывалось как нельзя лучше до того момента, как мы не посмотрели видео с презентацией кейса от кейсодержателя, что на выходе они ждут:
1) локальное решение, работающее без доступа в сеть;
2) интегрированные офисные пакеты посредством макросов.
Наша команда разделилась на 2 лагеря: половина настаивала на четком соблюдении требований кейсодержателя, в противном случае смене кейса, вторая настаивала на первичном варианте решения ( оффтоп: подобное решение выстрелило у других ребят и они выиграли).
Долгое время споры не утихали по данному поводу, все же решили сменить первоначальный выбор на кейс Почты России: Разработка прототипа системы обработки и подписания документов с использованием электронной подписи, применяемой для работы с обращениями граждан.
Формулировка кейса:
Согласно действующему законодательству «Почта России» обязана официально отвечать на поступающие запросы граждан, связанные с работой компании и качеством оказания услуг. Обращения поступают в профильное подразделение, где, после подготовки ответа, подписываются, сканируются и отправляются в соответствующий филиал или автору обращения. Процесс является трудоемким и ресурсоемким с точки зрения ручных операций и расходных материалов (бумага, расходы на оргтехнику). Участникам хакатона предлагается разработать программное решение - модуль подписания документов с помощью электронной подписи, который расширит функциональность существующей системы работы с обращениями (Террасофт Creatio). Модуль должен формировать электронную подпись для каждого файла отдельно и перед подписанием проверять срок действия сертификата электронной подписи уполномоченного сотрудника.
Почему он? У нас было понимание как работать с электронной подписью на Open source решениях: OpenSSL. Пригодился опыт Ильи в разработке СЭД - он знал о существовании php библиотеки tcpdf для генерации файла pdf с возможностью встроить электронную подпись. Плюс на текущем проекте pirs.online мы уже копали эту тему, и оттого данной задачей заниматься было приятно вдвойне.
Вся наша подготовка к финалу заключалась в обсуждениях плана решения и договоренности с родными, что мы опять пропадем на работе на все выходные.
Панические атаки и ведро валерьянки
В пятницу вечером мы остались в офисе, получили подробное техническое задание кейса и приступили к решению. Последнее вранье - мы начали неистово паниковать, так как в технических ограничениях к решению значилось требование в реализации на ASP.NET.
Написали в чат поддержки ЦП (хвала за неиссякаемое терпение Ирине с вопросом: что нам делать и можно ли сменить кейс? К счастью, организаторам удалось переубедить представителей Почты России снять это ограничение и только тогда мы смогли приступить к решению.
А что потом? Технические подробности
Дальше пошло по накатанной: первый чекпоинт и наши идеи по реализации, полученное одобрение экспертов и практически бессонная ночь работы.
Нам повезло: роли между командой у нас распределены четко, и каждый занимается своей задачей. Марина - презентация и вычитка спитча Ильи. Илья отвечает за спитч, архитектуру, бэк. Дима и Кирилл - фуллстеки.
С точки зрения технической реализации функционал прототипа выглядел просто:
вход пользователя под одной из двух ролей: гость и администратор;
формирование обращения администратору (почте РФ);
рассмотрение обращения и формирование ответа;
можно прикрепить файлы к обращению и ответу;
создать сертификат пользователя в личном кабинете;
подписать файлы ответа электронной подписью;
выгрузить обращение с электронной подписью;
проверить электронную подпись в обращении.
Структура базы данных прототипа уместилась всего в 3 таблицы, размещенных в PostgreSQL:
user - таблица пользователей с реквизитами и типами;
message - таблица обращений и ответов. По сути это переписка клиента и администратора;
file - таблица файлов, прикрепленных к обращениям и ответам.
Благодаря большому опыту команды с php-фреймворком Yii2 мы в короткие сроки разработали основной функционал приложения. А вот задача интеграции функционала по работе с электронными подписями была трудоемкой и нетривиальной.
Для работы с электронными подписями мы решили использовать OpenSSL, как открытый стандарт де-факто по работе с электронными подписями.
Как и ожидалось библиотека очень мощная, но из коробки не поддерживает отечественные алгоритмы шифрования. Какое-то время ушло на интеграцию и настройку криптографического движка (libengine-gost-openssl 1.1) на алгоритмы ГОСТ, в частности ГОСТ-2012. Затем мы создали и настроили удостоверяющий центр.
Второй чекпоинт: показали первичный вид презентации. Первый провал - ошиблись с формулировкой проблематики кейса, близко, но не то. Вот почему важно презентацию хотя бы раз показать экспертам!
Пилим прототип дальше
PHP содержит функции для работы с openssl по созданию сертификатов и подписи файлов, но после тщательного изучения документации, выяснилось, что переключить openssl engine на ГОСТ невозможно.
Поэтому команда принимает решение перейти к работе с openssl через консоль. Решение выглядит менее изящным, зато предоставляет доступ ко всей функциональности openssl.
Создание сертификата пользователя:
exec("openssl req -nodes -newkey gost2012_512 -keyout $eSignPath/client.key -pkeyopt paramset:A -out $eSignPath/client.csr -subj \"/C=RU/ST=Udm/L=Izhevsk/O=IT/OU=animals/CN=user-{$user->id}\" -config $caPath/openssl.cnf ");
exec("openssl ca -engine gost -keyfile $caPath/ca.key -cert $caPath/ca.crt -in $eSignPath/client.csr -out $eSignPath/client.crt -batch -config $caPath/openssl.cnf 2>&1", $output);
где $eSignPath
- путь до папки с ключами пользователей, а $caPath
- путь до папки удостоверяющего центра.
Удаление сертификата пользователя:
exec("openssl ca -config $caPath/openssl.cnf -keyfile $caPath/ca.key -cert $caPath/ca.crt -revoke $eSignPath/client.crt 2>&1", $output);
exec("openssl ca -gencrl -config $caPath/openssl.cnf -keyfile $caPath/ca.key -cert $caPath/ca.crt -out $caPath/crl.pem 2>&1", $output);
где $eSignPath
- путь до папки с ключами пользователей, а $caPath
- путь до папки удостоверяющего центра.
Подписание файла сертификатом пользователя:
exec("openssl smime -engine gost -sign -in $fp -out $fp.sig -nodetach -binary -signer $clientKeysPath/client.crt -inkey $clientKeysPath/client.key -outform SMIME 2>&1", $output);
где $fp
- путь до файла, $clientKeysPath
- путь до папки с ключами пользователя.
Проверка подписи файла:
$output = exec("openssl cms -engine gost -verify -in $sigPath -inform SMIME -CAfile $pathCA/ca.crt -out $fp -certsout $clientKeysPath/client.crt 2>&1");
где $fp
- путь до файла, $clientKeysPath
- путь до папки с ключами пользователя, $sigPath
- путь до электронной подписи.
Долгие часы настройки openssl и его интеграции в веб-приложение увенчались успехом. К третьему чекпоинту прототип уже мог создавать сертификат пользователя, подписывать сертификатом обращение, а затем проверять подпись. Решение было рабочим, но чутье подсказывало: не хватает какой-то главной фишки.
Появилась идея: Илья вспоминает, что в одной не самой популярной библиотеке tcpdf по формированию pdf файлов была возможность встраивания электронной подписи в pdf файл. А это значит, что можно из обращения создать pdf файл и сразу встроить в него электронную подпись.
Ночь субботы подходит к концу: мы голодные и злые все еще работаем в офисе. Тошнит от доставок, работы вагон, но мы верим в команду и в то, что победа близка. Договариваемся ехать домой, и доделывать уже там.
Начинаем реализовывать и понимаем на сколько в библиотеке tcpdf захардкодена работа с openssl. Вылазят проблемы невозможности смены движка и другие конфликты библиотеки с нашим решением. Создаем потомка библиотеки и заменяем всю генерацию подписи с хардкода openssl на наш костыль (херак-херак, и в продакшн) через локальный метод api:
$fields = [
'r' => 'api/sign',
'filePath' => $tempdoc,
'userId' => $user->id,
];
$query = http_build_query($fields);
$ch = curl_init();
$host = \Yii::$app->params['apiHost'] ?? '';
curl_setopt($ch, CURLOPT_URL, $host . '/index.php?' . $query);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$signature = curl_exec($ch);
/*if (empty($this->signature_data['extracerts'])) {
openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
} else {
openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED, $this->signature_data['extracerts']);
}*/
И все же мы успеваем в последний момент и к утру воскресенья прототип полностью работает!
Последний рывок и мы у цели
В середине дня воскресенья Илья подключается по Zoom к защите решения, жюри неумалимы, ну а мы ждем результатов.
Было страшновато: осознание, что ты соревнуешься с лучшими (в финал попали топ 5 команд из отборочных региональных туров) подстегивало выкладываться на полную.
Неожиданно результатов пришлось ждать до вечера, хотя на Северо-Западном хабе объявили победителей чуть ли не через час после защит. Время тянулось как доставка Почты России.
Офтоп: мы победители! 750 тысяч на команду, Карл! 750 за 2 дня, Карл! А значит едем на грандфинал Цифрового прорыва в Москву!
Репозиторий нашего решения