تخطَّ إلى المحتوى

التحقق من الأدلة دون اتصال

حزمة الأدلة ملف ZIP يُصدَّر من لوحة التحكم. تحتوي على سجلات الموافقة، والملفات اللازمة لفحصها، وسكريبتات تنفّذ الفحوص. لا شيء في هذه الصفحة يحتاج إلى اتصال بالشبكة أو إلى خادم HashEntry.

تذكر هذه الصفحة ما يثبته كل فحص. لا يوجد فحص واحد يجعل السجل “محصنًا ضد التلاعب”؛ لكنها مجتمعةً تتيح لطرف ثالث اكتشاف التغييرات وربط السجل بنقطة زمنية.


الملفالمحتوى
consent_data.jsonالسجلات المصدَّرة. لكل سجل كتلة proof تحتوي على الحمولة التي جرت تجزئتها.
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).

Terminal window
# Linux / macOS
chmod +x verify.sh && ./verify.sh
Terminal window
# Windows
powershell -ExecutionPolicy Bypass -File .\verify.ps1

تفحص السكريبتات المجاميع الاختبارية للملفات، والبيان (manifest)، والأختام الزمنية، وإثباتات Merkle. وهي لا تعيد حساب تجزئة الإثبات، لأن ذلك يحتاج إلى المفتاح السري للمتحكم في البيانات (انظر أدناه).


Terminal window
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 بأحرف صغيرة

تحتوي الحمولة دائمًا على هذه المفاتيح الأحد عشر (القيمة الغائبة تكون 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 نفسها وتُظهر أن هذه القيمة كانت موجودة في الوقت المذكور.

Terminal window
# بالمفتاح: أعد حساب تجزئة الإثبات لسجل واحد (الحمولة القانونية في payload.json، بلا سطر جديد في النهاية)
openssl dgst -sha256 -hmac "$TENANT_SECRET_KEY" payload.json

السجلات القديمة (hash_version: 1) تستخدم SHA-256( json(الحمولة بمفاتيح المستوى الأعلى مرتبة) || key ) وتحتوي على بيانات شخصية في الحمولة مباشرةً.

في timestamps.tsv صف واحد لكل رمز:

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

البيانات المختومة سلسلة hex بأحرف صغيرة: proof_hash لسجل، أو جذر Merkle، أو package_hash. وقّعت هيئة الختم الزمني (TSA) قيمة SHA-256 لمحارف ASCII في تلك السلسلة، بلا سطر جديد في النهاية:

Terminal window
printf '%s' "<stamped data>" > data.txt
openssl ts -verify -data data.txt -in <file.tsr> -CAfile <tsa_root.pem>
  • الشهادة الجذرية للـ TSA ليست في الحزمة. احصل عليها من هيئة الختم الزمني نفسها؛ فالجذر الذي يقدّمه الطرف الذي تفحص بياناته لا يثبت شيئًا.
  • رموز بعض الهيئات تفشل في فحص ESSCertID الذي يجريه openssl ts -verify. تحقق عندئذ من التوقيع والسلسلة في خطوتين:
Terminal window
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، أو الأختام الزمنية لمزوّد مرخَّص بموجب القانون التركي) يتوقف على الهيئة وعلى الولاية القضائية.

4. إثباتات التضمين في Merkle

Section titled “4. إثباتات التضمين في Merkle”

عندما تُختم السجلات زمنيًا على دفعات، يغطي رمز واحد جذر 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 على محارف ASCII الـ 128 للقيمة hex(left) || hex(right) — أي سلاسل hex، لا البايتات الخام؛
  • الطبقة التي فيها عدد فردي من العقد تكرر عقدتها الأخيرة؛
  • لا توجد بادئة لفصل النطاق بين الأوراق والعقد.

للتحقق من مسار، ابدأ بـ cur = leaf وطبّق كل خطوة:

L:<h> → cur = sha256(<h> || cur)
R:<h> → cur = sha256(cur || <h>)
Terminal window
# خطوة واحدة، مثل "R:<h>"
cur=$(printf '%s%s' "$cur" "$h" | sha256sum | cut -d' ' -f1)

يجب أن تساوي النتيجة <root>. تحقق أيضًا من أن 0 <= index < leaf_count ومن أن عدد الخطوات يساوي عمق الشجرة الموافق لـ leaf_count — فقاعدة التكرار لا تربط حجم الشجرة بالجذر، ولذلك فهذه القيم جزء من الإثبات. وأخيرًا تحقق من <epoch.tsr> مقابل <root> كما في القسم 3: فهذا ما يثبت متى كان الجذر، ومن ثَم تجزئة الإثبات الخاصة بالسجل، موجودًا.


ما تثبته الفحوص مجتمعةً

Section titled “ما تثبته الفحوص مجتمعةً”
الفحصما يثبته
المجاميع الاختباريةملفات الحزمة متسقة داخليًا.
تجزئة الإثبات (بالمفتاح)محتوى السجل يطابق التجزئة المحسوبة عند كتابته.
رمز RFC 3161القيمة المختومة كانت موجودة في موعد لا يتجاوز وقت الرمز، بحسب تلك الهيئة.
إثبات Merkle + رمز الحقبة (epoch)تجزئة الإثبات الخاصة بالسجل كانت جزءًا من دفعة كان جذرها موجودًا في ذلك الوقت.

ما لا تثبته: من كان الشخص (راجع identity_assurance في الموافقة على المستندات والتوقيع)، ولا أن السجل أُنشئ بصورة صحيحة أصلًا. السجلات التي ليس لها رمز ختم زمني تعتمد على تجزئة الإثبات وحدها، ومن ثَم على سرية المفتاح.