Данная статья написана в соавторстве с тимлидом @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 дня,
Карл! А значит едем на грандфинал Цифрового прорыва в Москву!
Репозиторий нашего
решения