Спустя десятки лет после появления землян на планете Плюк на место КЦ пришёл КУ-АР в качестве самого ценного ресурса для чатлан и пацаков. Желающие приобрести себе в будущем малиновые штаны представители двух народов нашли способ подделки этого ценного средства, вследствие чего понадобилось внедрение способа его проверки на подлинность. В этой статье я расскажу о том, как я разрабатывал Android-приложение для сканирования и верификации сертификатов вакцинации, а также о том, что из этого в итоге вышло. Кому-то приходят идеи ночью, во сне, кто-то получает возможность для погружения в «поток» во время упорной работы, а я вот испытываю бесконечный поток мыслей, в котором мелькают различные идеи, во время чистки зубов. Так и на этот раз, в октябре, во время чистки зубов, на фоне массовой публикации постановлений с ковидными ограничениями мне в голову пришла мысль о потенциальной возможности обхода проверки QR-кодов за счёт использования фишинговых сайтов с поддельными сертификатами вакцинации. Тогда я сразу понял, что я не один такой умный (что позже подтвердилось), и что потенциально это может стать серьёзной проблемой для полноценной реализации антиковидных мер. Текущую ситуацию с фишинговыми сайтами, являющимися клонами страниц с данными сертификатов вакцинации я описал в другой статье ранее. На тот момент я не слышал о существовании других приложений в России, созданных именно под сертификаты вакцинации, но при этом решил специально не проверять эту информацию (А что из этого потом вышло вы узнаете позже), поэтому спустя два дня я приступил к разработке собственного решения. Концепция функционирования приложения является достаточно простой.  На вход должно поступить содержимое QR-кода после его сканирования, после чего это содержимое проверяется на факт того, является ли оно ссылкой. В случае, если QR-код содержит что-то кроме ссылки, то приложение должно вывести ошибку. Если всё же приложение содержит ссылку, то происходит проверка домена этой ссылки, а также подкаталогов и имён страницы, и если ссылка не соответствует заданным условиям, то приложение также выводит ошибку. В случае же, если ссылка валидная, то далее идёт запрос данных сертификата, их вывод на экран, а также проверка на повторное использование этого сертификата, после чего данные сохраняются в историю сканирования. В историю сканирования также сохраняются и ошибки вместе с содержимым QR-кода для возможности оценки статистики использования QR-кодов с курицей по скидке. Публичный репозиторий с кодом проекта доступен на GitHub. Для сканирования QR-кодов я использовал библиотеку, основанную на библиотеке ZXing.  За сканирование и декодирование QR-кода в приложении отвечает процедура codeScannerProc(), в которой используется метод подключённой библиотеки для декодирования содержимого QR-кода onDecoded(): Внутри метода onDecoded(), в который передаётся содержимое QR-кода находится переопределённый метод run(), который вызывает метод проверки данных, содержавшихся в QR-коде. Для того, чтобы отбросить любые данные кроме ссылки на сертификат вакцинации, используется метод checkContent(), в который передается строка с содержимым QR-кода: В начале происходит проверка на факт того, что содержимое вообще является ссылкой. Для этого используется метод isURL() класса QuickResponseCodeURL. Метод isURL(), как и последующие методы проверки содержимого QR-кода использует регулярное выражение для возвращения результата в виде boolean-значения.  Для проверки на факт соответствия ссылке используется шаблон регулярного выражения в виде экземпляра класса Pattern – urlPattern (для шаблона ссылки используется стандарт RFC 3986). При помощи класса Matcher и метода matches() мы получаем результат «true» в том случае, если содержимое соответствует шаблону ссылки, и, соответственно false – во всех других случаях. В ситуации же, если строка содержимого совпадает с шаблоном ссылки, происходит проверка на соответствие единственно верному доменному имени, а также на шаблон пути, который содержится в ссылке. Для этого используется метод isValidURL(). В процессе изучения предметной области был сделан вывод о том, что правильные ссылки должны содержать домен «gosuslugi.ru», а также один из возможных путей: /covid-cert/verify/**************** (где * – это цифры номера сертификата);\ /vaccine/cert/verify//************************************ (где * – это знаки некого хэш-кода); /covid-cert/status/************************************ (где * – это знаки некого хэш-кода). Первый и второй тип путей обычно используются для сертификатов вакцинации, а последний – для временных сертификатов. Аналогично проверке содержимого на соответствие шаблону ссылки происходит проверка на валидность ссылки при помощи экземпляров класса Pattern validUrlDomain и urlPathPattern: В ситуации, когда содержимое QR-кода не является ссылкой или содержит невалидную ссылку, на экран выводится соответствующее диалоговое окно, а также происходит сохранение результата сканирования вместе с датой и временем сканирования для осуществления возможности ведения статистики.  В случае, когда ссылка валидная, открывается новый экран CertificateActivity для извлечения данных сертификата. Для получения данных используется внутренний класс FetchJsonData, который является наследником класса AsyncTask, что необходимо для выполнения GET-запроса в фоновом режиме при помощи переопределённого метода doInBackGround() и метода fetch(). Данные сертификата (если он существует) содержатся в виде JSON-объекта. JSON (JavaScript Object Notation)-объект – это текстовый формат обмена данными между сервером и клиентом. Для того, чтобы получить JSON-объект при выполнении GET-запроса, необходимо знать ссылку, по которой осуществляется доступ к этим текстовым данным. В процессе изучения предметной области было выяснено, что структура ссылки JSON-объекта зависит от типа ссылки сертификата (которых, как указано выше, найдено 3 типа). Поэтому, последующего запроса происходит преобразование ссылки посредством её перестройки: Затем, при помощи экземпляра класса HttpURLConnection осуществляется соединение по адресу преобразованной ссылки для последующей возможности считать данные входного потока, используя класс InputStream. Данные JSON-объекта в виде строки преобразуются в экземпляр класса JSONObject для более удобной работы с последующим извлечением данных. Чтобы извлечь конкретные данные сертификата используется метод parseJson() класса ParseCertificateJson. Данные и их расположение внутри объекта JSON отличается в зависимости от типа сертификата и типа ссылки, поэтому в классе ParseCertificateJson имеется несколько методов для извлечения информации о владельце сертификата. В качестве примера для одного из типов сертификата приведён фрагмент кода (для других типов желающие могут посмотреть исходный код на странице проекта): После получения информации о сертификате данные выводятся на экран примерно в том же формате, что и на официальном государственном ресурсе «Госуслуги». Для избежания возможности использования одного и того же сертификата несколькими людьми (особенно подряд) осуществляется проверка на переиспользование сертификата при помощи метода checkPotentialCertificateReuse(), который запрашивает историю сканирования, преобразует её в структуру данных ArrayList и производит в цикле поиск по элементам списка сертификата с аналогичным номером.  В случае, если сертификат действительно повторно используется, на экран выводится соответствующее диалоговое окно с рекомендованными инструкциями по дальнейшим действиям. Чтобы фиксировать повторное использование одного и того же сертификата или иметь возможность сбора статистики по использованию невалидных ссылок или данных, а также разных типов сертификатов, в приложении сохраняется история сканирования. История сканирования хранится в файле в закрытом режиме, который позволяет сделать его недоступным пользователю для прямого взаимодействия без root-прав. Для работы с хранением истории сканирования используются классы HistoryFileInputOutput и HistoryFileParser. В первом определены методы, осуществляющие операции с файлом (создание, запись, чтение и очистка), а во втором – методы, производящие преобразование хранящихся в файле данных в ArrayList с экземплярами класса QuickResponseCodeHistoryItem (для поиска возможного переиспользования и последующей распечатки истории сканирования). Данные хранятся в файле в следующем формате: Для невалидных данных и ссылок: qrCodeType – тип QR-кода; content – содержимое QR-кода;  currentTime – дата и время сканирования. Для сертификатов: qrCodeType – тип QR-кода; certificateReuse – информация о переиспользовании сертификата (по умолчанию имеет значение «false»); type – тип сертификата (сертификат вакцинации, сертификат переболевшего, временный сертификат вакцинации или результат ПЦР-теста); title – название сертификата; status – статус действительности сертификата;  expiredAt – дата истечения срока действия сертификата;  validFrom – дата начала действия сертификата (для временных сертификатов);  isBeforeValidFrom – статус начала действия сертификата (для временных сертификатов);  fio – ФИО владельца сертификата;  enFio – ФИО владельца сертификата на латинице;  recoveryDate – дата выздоровления (для сертификатов переболевших);  passport – данные паспорта владельца сертификата;  enPassport – номер загранпаспорта владельца сертификата;  birthdate – дата рождения владельца сертификата;  currentTime – дата и время сканирования. Пример фрагмента данных, хранящихся в файле (персональные данные закрашены): Значения qrCodeType в зависимости от типа QR-кода: 1 – для невалидных данных; 2 – для невалидных ссылок; 3 – для сертификатов, о которых найдена информация; 4 – для сертификатов, информация о которых не найдена. Если QR-код не содержит определённых данных, то их значение равно «0». Хранение информации в файле не является оптимальным решением, но вполне удовлетворяет на этапе Pet-проекта без создания системы авторизации и отправки данных в облако.  Благодаря сохранению в файле информации о дате и времени сканирования приложение позволяет вывести достаточно подробную историю сканирования при помощи классов QuickResponseCodeHistoryActivity и QuickResponseCodeHistoryRecViewAdapter. История сканирования отображает статус QR-кода при помощи прокручивающегося текста и в виде цветного изображения слева: Зелёным выделяются подтверждённые сертификаты (UPD: во время написания статьи была добавлена возможность обработки QR-кодов ПЦР-тестов, поэтому теперь зелёным выделяются ещё и отрицательные ПЦР-тесты); Жёлтым выделяются повторно использующиеся сертификаты; Красным выделяются невалидные ссылки, данные, а также сертификаты, информация о которых не найдена (UPD: во время написания статьи была добавлена возможность обработки QR-кодов ПЦР-тестов, поэтому теперь красным выделяются ещё и положительные ПЦР-тесты). Также при желании можно развернуть информацию о конкретном QR-коде и посмотреть содержащиеся в нём данные. Как было указанно во введении, после того, как мне в голову пришла идея, я не стал тщательно искать информацию о существовании подобного решения на российском рынке мобильных приложений, так как изначально посчитал, что если я об этом не слышал, то и вряд ли такое приложение существует.  В принципе я оказался прав, так как отдельного приложения-сканера для верификации QR-кодов действительно не существует в России на момент написания этого раздела статьи. Но на вторые сутки разработки я узнал о том, что есть встроенный сканер в приложении «Госуслуги СТОП Коронавирус», что помогло осознать достаточно серьёзную ошибку в подготовке к началу разработки. Разработку демоверсии приложения я всё-таки закончил, хотя и работал впоследствии с куда меньшим энтузиазмом, чем в самом начале. Но всё время я уже не мог отделаться от мысли о том, что вместо этого я мог бы заниматься чем-то более полезным несмотря на то, что фактически в процессе работы я всё равно получал опыт внедрения некоторых новых для себя элементов интерфейса.  Из этой истории я вынес для себя важный урок в правильном подходе к разработке проектов, который заключается не только в тщательном анализе предметной области перед процессом написания кода, но также и в обязательном поиске информации о существовании похожих решений. Разработчик android-приложений