Исследование молдавского сертификата COVID-19
С 11 октября 2021 года взамен старому COVID-19 сертификату пришел новый формат. При этом заявлено о том, что формат должен соответствовать европейскому. Это уже становится интересно.
Что было раньше на QR-коде
Как принято, начнем с исторической справки.
Вид сертификата со временем претерпевал изменения.
5 февраля 2021 года минздрав в приложении к приказу №93 опубликовал первую форму сертификата:
Как видно, никаких машиночитаемых областей нет и достоверность гарантируется подписью и печатью центра вакцинации.
Однако, со временем на сертификате появился первый QR-код.
QR-код был небольшой и хранил единственную информацию — IDNP (уникальный идентификационный номер в Молдове). Для внутреннего использования такой информации уже достаточно, чтобы местным службам проверять достоверность документа, однако, зашла речь о признании сертификатов за рубежом, а давать доступ к базе по IDNP не очень, видимо, хотелось, местным службам.Поэтому в июле 2021 года появился сайт https://certificate-covid.gov.md/ где можно скачивать сертификаты, при создании которых, как утверждалось, минздрав "учитывала все рекомендации ЕС, чтобы их могли признавать на международном уровне."
Действительно, тут появилось дополнительно массивные QR-коды. Появилась какая-то надежда, что тут уж точно будет соответствие международным стандартам, но эти коды ведут на страницу https://certificate-covid.gov.md/verifym.php?coden=DsrzEXEuvHi1yyh6Dna%2Brz25%2FN4WpSM… Дальше ссылка обрезана, но понимание приходит, что о соответствии с европейскими стандартами тут речь не идет.
По параметру codegen лежит явно base64, но что за массив байтов внутри определить не удалось, да и не похож он ни на что. Согласно европейскому стандарту там должна быть строка, начинающаяся с префикса HC1, или HC2, то есть Health Certificate Version 1, или 2. А дальше после двоеточие идет base45 c запакованным в zip объектом, но об этом позже.
По ссылке можно узнать, имя, фамилию, год рождения, а также информацию о вакцинации. После последнего обновления, о котором ниже на этой странице сохранилось только IDNP, фамилия, имя и появилось требование заново сгенерировать сертификат.
Решение не внедрять QR-код европейского стандарта, а отдавать ссылку в принципе понятно — оно требует дополнительного ПО, при чем не только у местных, но и за рубежом. А с ссылкой все становится проще — проверяющий переходит просто на государственный сайт, где подтверждается вакцинация. Можно, конечно, пользоваться уже готовым и установленным ПО ЕС по проверке сертификатов, но тут тоже получается проблема — ЕС не признает в общем виде молдавские сертификаты, поэтому и электронная подпись сертификата будет отображаться как неверная, следовательно сертификат станет недействительным. А открыв сайт можно будет просто всё узнать.
Разбор нового сертификата
Тут уже становится интереснее. Хоть оно по-прежнему ведет на https://certificate-covid.gov.md/verifym.php?coden=HC1%3ANCFOXN%25TSMAHN-HF+SA%2AM+XLT81… (далее ссылка обрезана) с краткой информацией, но тут мы уже видим несколько новых полей.
Можно увидеть уникальный номер сертификата Unique Vaccination Certificate/Assertion Identifier (UVCI), который описан в европейской спецификации.
А еще в ссылке можно увидеть заветный HC1, то есть Health Certificate Version 1. Почему не второй версии, непонятно, но уже интересно.
Что уже напрягает? То, что формат base45, специально созданный для того, чтобы сделать QR-код более читаемым портится преобразованием в URL и вся его идея от этого ломается.
Распаковываем подпись
В первую очередь, делаем urldecode параметра coden и убираем HC1: из начала.
Потом расшифровываем base45 (далее примеры даны на языке python) и распаковываем zlib-ом:
import base45 import zlib zlibdata = base45.b45decode(cert) cbordata = zlib.decompress(zlibdata)
Согласно документации ответ упакован в формат COSE, это бинарное представление объекта (формат CBOR) с добавленной возможностью подписи и шифрования. Интересный выбор, потому что для сравнения объект в JSON-LD в 1271 байт превращается в 461 в CBOR-LD.
Расшифровываем его с помощью библиотеки cose:
from cose.messages import CoseMessage cose_msg = CoseMessage.decode(cbordata)
Это достаточно интересный формат, но не углубляясь, из полезного мы можем извлечь: подпись, алгоритм подписи (в этом случае ES256, он же SHA256withECDSA), идентификатор ключа (первые 8 байт хэша ключа), а также полезное содержимое контейнера.
Содержимое, как и повелось, снова является объектом CBOR, расшифровываем:
import cbor2 cbor2.loads(cose_msg.payload)
И тут мы получаем уже интересные данные (с немного изменёнными личными данными):
{ "1":"MD", "6":1634621519, "4":1650173519, "-260":{ "1":{ "ver":"1.3.0", "nam":{ "fn":"VELIKODNIY", "fnt":"VELIKODNIY", "gn":"VITALIY", "gnt":"VITALIY" }, "dob":"1970-01-01", "v":[ { "tg":"840539006", "vp":"1119305005", "mp":"EU/1/21/1529", "ma":"ORG-100001699", "dn":2, "sd":2, "dt":"2021-01-01", "co":"MD", "is":"Ministry of Health" "ci":"URN:UVCI:01:MD:SIAVAAAAAAAAAAAAAAAAAAAAAAA#5", } ] } } }
Тут уже все просто, ключ 1 отвечает за страну (международную организацию), выдавшую сертификат, ключ 6 отвечает за дату генерации сертификата в формате Unix time, ключ 4 за дату истечения сертификата. Судя по данным, сертификат валиден в течении 180 дней с момента генерации файла.
А в -260 поле уже лежит полезная информация сертификата, описанная в json спецификации формата.
Разбираем информацию внутри
Первым полем идет ver, тут все понятно: версия спецификации, в данный момент 1.3.0.
Поле nam содержит фамилию (fn), стандартизированную фамилию (fnt)— транслитерированную фамилию по конвенции ICAO Doc 9303 Part 3, имя (gn), стандартизированное имя (gnt), а далее идет дата рождения (dob)
Потом разбираем что внутри блока (v) — информации о вакцинации. Всего возможно 3 группы, кроме v есть еще t для тестов, r (recovery) — для информации о переболевшем. А внутри каждой группы может быть только 1 вхождение. Будем рассматривать информацию из следующей спецификации.
- tg (target) — заболевание, против которого направлена вакцинация. Берется значение из специального списка возможных и пока там только одна строка 840539006 для COVID19 из медицинской номенклатуры SNOMED CT.
- vp (vaccine/prophylaxis) — тип вакцины из SNOMED CT, или ATC Classification. В данном случае 1119305005 означает SARS-CoV2 antigen vaccine.
- mp (medical product) — код зарегистрированной вакцины. Список можно найти в спецификации, а EU/1/21/1529 из примера соответствует вакцине под названием Vaxzevria.
- ma (manufacturer, или marketing authorisation — тут не понять что они хотели сократить) — аналогично предыдущему полю, код производителя, ORG-100001699 соовтетствует AstraZeneca AB.
- dn (dose number) — последовательный номер дозы, введенной в течении этой вакцинации. Бустерная доза тоже идет в счет.
- sd (dose in series) — сколько всего доз. Такое число надо указывать, потому что в разных странах свои правила по введению количества доз, к примеру, переболевшим могут ставить только одну дозу.
- dt (date) — дата вакцинации.
- co (country) — код страны, рекомендуется ЕС для стран, с которыми планируется обмен информацией.
- is (certificate issuer) — наименование организации, выпустившей сертификат. В данном случае это минздрав.
- ci (certificate identifier) — уникальный идентификатор сертификата. Создается по спецификации.
Насчет уникального идентификатора сертификата есть интересная особенность — кроме указанной страны и версии, возможны 3 сценария генерации:
- с указанием просто уникальной строки
- с указанием организации, выпустившей сертификат, вакцины и уникальной строки
- с указанием организации, выпустившей сертификат и уникальной строки
Видимо, тут решили просто добавить уникальную строку.
А в конце через # обязательная проверочная сумма, рассчитанная от всего UVCI, рассчитанный алгоритмом Луна, прямо как на банковских карточках.
Обновление от 31.12.2021
С какого-то момента начался генерироваться сертификат такого же вида, но уже с QR-кодом, в котором только текст HC1:NCFOXN…, а не ссылка как ранее. При этом старая ссылка с https://certificate-covid.gov.md/verifym.php?coden=HC1:NCFOXN... перестала работать.