Перейти к содержимому

Офлайн-проверка доказательств

Пакет доказательств — это ZIP-файл, экспортируемый из панели управления. Он содержит записи о согласиях, файлы, необходимые для их проверки, и скрипты, которые выполняют проверки. Ничто из описанного на этой странице не требует сетевого подключения или сервера HashEntry.

На этой странице указано, что устанавливает каждая проверка. Ни одна проверка по отдельности не делает запись «защищённой от изменений»; вместе они позволяют третьей стороне обнаружить изменения и привязать запись к моменту времени.


ФайлСодержимое
consent_data.jsonЭкспортированные записи. У каждой записи есть блок proof с хешируемыми данными (payload).
manifest.json, metadata.jsonИдентификатор пакета, версия спецификации, количество записей, package_hash.
checksums.sha256SHA-256 каждого файла в пакете.
timestamps.tsv + *.tsrТокены меток времени RFC 3161 (tsa_consent_<id>.tsr, tsa_epoch_<id>.tsr, package.tsr) и то, что охватывает каждый из них.
merkle_proofs.tsvДоказательства включения Merkle (для записей, получивших метку времени в составе пакетов).
verify.sh, verify.ps1, README_*.txt, proof_verification.txtСкрипты проверки и инструкции.

Пакеты бывают двух видов: неподписанные (только контрольные суммы) и подписанные (хеш пакета дополнительно снабжён меткой времени RFC 3161, package.tsr).

Окно терминала
# Linux / macOS
chmod +x verify.sh && ./verify.sh
Окно терминала
# Windows
powershell -ExecutionPolicy Bypass -File .\verify.ps1

Скрипты проверяют контрольные суммы файлов, манифест, метки времени и доказательства Merkle. Они не вычисляют заново хеш доказательства, потому что для этого нужен секретный ключ оператора (контролёра) данных (см. ниже).


Окно терминала
sha256sum -c checksums.sha256 # macOS: shasum -a 256 -c checksums.sha256
sha256sum consent_data.json # должно совпадать с "package_hash" в manifest.json

Это показывает, что файлы — те самые, что перечислены в пакете. Само по себе это не показывает, кто создал пакет и когда: любой может пересчитать контрольные суммы после изменения файла. Для этого и предназначена метка времени в подписанном пакете.

У каждой записи есть proof_hash. Для текущих записей (hash_version: 2):

proof_hash = HMAC-SHA256( key = секретный ключ тенанта, message = canonical_json(payload) ) → hex в нижнем регистре

Payload всегда содержит эти одиннадцать ключей (отсутствующее значение равно null):

v (= 2), site_id, document_hash, consent_type, decision, categories, timestamp,
pii_digest, signer_digest, custom_document_hash, signature_hash
  • timestamp — это время UTC с точностью до секунды, YYYY-MM-DDTHH:MM:SS+00:00; оно равно времени создания записи.
  • Персональные данные не попадают в хеш напрямую. Они попадают в него через дайджесты с солью: pii_digest = SHA-256( pii_salt || canonical_json({ip_address, user_agent, visitor_id}) ) и signer_digest = SHA-256( pii_salt || canonical_json(поля сведений о подписанте) ) (или null, когда нет ни подписи, ни заявления о личности). Когда запись анонимизируется, персональные поля и соль удаляются, но дайджесты остаются, поэтому хеш доказательства по-прежнему можно вычислить заново, тогда как дайджесты уже невозможно связать с IP-адресом или адресом электронной почты.

Канонический JSON (HashEntry Canonical JSON v2 — это не RFC 8785):

  1. UTF-8, без пробельных символов между токенами.
  2. Ключи объектов отсортированы по порядку байтов UTF-8, на каждом уровне.
  3. Порядок элементов массива сохраняется.
  4. Пустой объект и пустой массив оба записываются как [].
  5. В строках экранируются только ", \ и управляющие символы U+0000–U+001F; / и символы не из ASCII не экранируются.
  6. Числа — только целые.
  7. true, false, null — в нижнем регистре.

Кто может его проверить. Хеш доказательства — это хеш с ключом. Для его повторного вычисления нужен секретный ключ тенанта, поэтому проверить его может оператор (контролёр) данных (у которого есть ключ) или эксперт, которому оператор передаст ключ. Без ключа третья сторона не может вычислить его заново — и ей это не нужно: описанные ниже проверки метки времени и Merkle работают с самим значением proof_hash и показывают, что это значение существовало в указанное время.

Окно терминала
# С ключом: заново вычислить хеш доказательства одной записи (канонический payload в payload.json, без завершающего перевода строки)
openssl dgst -sha256 -hmac "$TENANT_SECRET_KEY" payload.json

Устаревшие записи (hash_version: 1) используют SHA-256( json(payload с отсортированными ключами верхнего уровня) || key ) и содержат персональные данные непосредственно в payload.

В timestamps.tsv — одна строка на каждый токен:

<file.tsr> <kind> <stamped data> <provider> <level at issuance>

Заверенные данные (stamped data) — это шестнадцатеричная строка в нижнем регистре: proof_hash записи, корень Merkle или package_hash. Служба меток времени (TSA) подписала SHA-256 от символов ASCII этой строки, без завершающего перевода строки:

Окно терминала
printf '%s' "<stamped data>" > data.txt
openssl ts -verify -data data.txt -in <file.tsr> -CAfile <tsa_root.pem>
  • Корневой сертификат TSA не входит в пакет. Получите его у самой службы меток времени; корневой сертификат, поставляемый стороной, чьи данные вы проверяете, ничего бы не доказывал.
  • Токены некоторых служб не проходят проверку ESSCertID в openssl ts -verify. В таком случае проверьте подпись и цепочку в два шага:
Окно терминала
openssl ts -reply -in <file.tsr> -token_out -out token.der
openssl cms -verify -inform DER -in token.der -CAfile <tsa_root.pem> \
-purpose timestampsign -binary -out tstinfo.der
openssl ts -reply -in <file.tsr> -text # сравните "Message data" с SHA-256 заверенных данных

Строка с уровнем simulated — это тестовая метка времени: у неё нет файла .tsr и нет доказательственной ценности.

Действительный токен показывает, что заверенное значение существовало не позднее времени, указанного в токене, — согласно этой службе меток времени. Обладают ли метки времени конкретной службы правовой презумпцией (например, квалифицированные метки времени по eIDAS или метки времени уполномоченного провайдера по турецкому праву), зависит от службы и от юрисдикции.

Когда записи получают метку времени в составе пакетов, один токен охватывает корень Merkle пакета, а каждая запись получает доказательство включения. merkle_proofs.tsv:

<consent_id> <leaf> <index> <leaf_count> <root> <epoch.tsr> <path>

Дерево построено по собственной бинарной схеме Merkle на SHA-256, принятой в HashEntry. Это не дерево RFC 6962 (Certificate Transparency), и инструменты для RFC 6962 его не проверят:

  • лист = proof_hash записи (64 шестнадцатеричных символа в нижнем регистре), без дополнительного хеширования;
  • узел = SHA-256 от 128 символов ASCII hex(left) || hex(right) — от шестнадцатеричных строк, а не от необработанных байтов;
  • уровень с нечётным числом узлов дублирует свой последний узел;
  • префикса для разделения доменов листьев и узлов нет.

Чтобы проверить путь, начните с cur = leaf и примените каждый шаг:

L:<h> → cur = sha256(<h> || cur)
R:<h> → cur = sha256(cur || <h>)
Окно терминала
# один шаг, например "R:<h>"
cur=$(printf '%s%s' "$cur" "$h" | sha256sum | cut -d' ' -f1)

Результат должен быть равен <root>. Также проверьте, что 0 <= index < leaf_count и что число шагов равно глубине дерева для leaf_count: правило дублирования не привязывает размер дерева к корню, поэтому эти значения являются частью доказательства. Наконец, проверьте <epoch.tsr> относительно <root>, как в разделе 3: именно это доказывает, когда существовал корень, а значит, и хеш доказательства записи.


Что проверки устанавливают в совокупности

Заголовок раздела «Что проверки устанавливают в совокупности»
ПроверкаЧто устанавливает
Контрольные суммыФайлы пакета внутренне согласованы.
Хеш доказательства (с ключом)Содержимое записи соответствует хешу, вычисленному при её создании.
Токен RFC 3161Заверенное значение существовало не позднее времени токена — согласно этой службе.
Доказательство Merkle + токен эпохиХеш доказательства записи входил в пакет, корень которого существовал в это время.

Чего они не устанавливают: кем был человек (см. identity_assurance в разделе Одобрение и подписание документов) и что запись изначально была создана корректно. Записи без токена метки времени опираются только на хеш доказательства, а значит, на секретность ключа.