التحقق من الأدلة دون اتصال
حزمة الأدلة ملف ZIP يُصدَّر من لوحة التحكم. تحتوي على سجلات الموافقة، والملفات اللازمة لفحصها، وسكريبتات تنفّذ الفحوص. لا شيء في هذه الصفحة يحتاج إلى اتصال بالشبكة أو إلى خادم HashEntry.
تذكر هذه الصفحة ما يثبته كل فحص. لا يوجد فحص واحد يجعل السجل “محصنًا ضد التلاعب”؛ لكنها مجتمعةً تتيح لطرف ثالث اكتشاف التغييرات وربط السجل بنقطة زمنية.
ما الذي تحتويه الحزمة
Section titled “ما الذي تحتويه الحزمة”| الملف | المحتوى |
|---|---|
consent_data.json | السجلات المصدَّرة. لكل سجل كتلة proof تحتوي على الحمولة التي جرت تجزئتها. |
manifest.json, metadata.json | معرّف الحزمة، إصدار المواصفة، عدد السجلات، package_hash. |
checksums.sha256 | SHA-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).
فحص سريع
Section titled “فحص سريع”# Linux / macOSchmod +x verify.sh && ./verify.sh# Windowspowershell -ExecutionPolicy Bypass -File .\verify.ps1تفحص السكريبتات المجاميع الاختبارية للملفات، والبيان (manifest)، والأختام الزمنية، وإثباتات Merkle. وهي لا تعيد حساب تجزئة الإثبات، لأن ذلك يحتاج إلى المفتاح السري للمتحكم في البيانات (انظر أدناه).
1. سلامة الملفات
Section titled “1. سلامة الملفات”sha256sum -c checksums.sha256 # macOS: shasum -a 256 -c checksums.sha256sha256sum consent_data.json # يجب أن يساوي "package_hash" في manifest.jsonيُظهر هذا أن الملفات هي المدرجة في الحزمة. وهو بمفرده لا يُظهر من أنشأ الحزمة ولا متى — فأي شخص يستطيع إعادة حساب المجاميع الاختبارية بعد تغيير ملف. ولهذا وُجد الختم الزمني على الحزمة الموقَّعة.
2. تجزئة الإثبات
Section titled “2. تجزئة الإثبات”لكل سجل قيمة 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_hashtimestampبتوقيت 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):
- UTF-8، بلا مسافات بيضاء بين الرموز النحوية.
- مفاتيح الكائنات مرتبة بحسب ترتيب بايتات UTF-8، في كل مستوى.
- ترتيب المصفوفات يُحفظ.
- الكائن الفارغ والمصفوفة الفارغة يُكتبان كلاهما
[]. - في السلاسل النصية لا يُهرَّب إلا
"و\ومحارف التحكم U+0000–U+001F؛ أما/والمحارف غير ASCII فلا تُهرَّب. - الأرقام أعداد صحيحة فقط.
trueوfalseوnullبأحرف صغيرة.
من يستطيع فحصها. تجزئة الإثبات تجزئة بمفتاح. إعادة حسابها تحتاج إلى المفتاح السري للمستأجر، ولذلك يستطيع فحصها المتحكم في البيانات (الذي يحوز المفتاح) أو خبير يعطيه المتحكم المفتاح. بدون المفتاح لا يستطيع طرف ثالث إعادة حسابها — ولا يحتاج إلى ذلك: ففحوص الختم الزمني و Merkle أدناه تعمل على قيمة proof_hash نفسها وتُظهر أن هذه القيمة كانت موجودة في الوقت المذكور.
# بالمفتاح: أعد حساب تجزئة الإثبات لسجل واحد (الحمولة القانونية في payload.json، بلا سطر جديد في النهاية)openssl dgst -sha256 -hmac "$TENANT_SECRET_KEY" payload.jsonالسجلات القديمة (hash_version: 1) تستخدم SHA-256( json(الحمولة بمفاتيح المستوى الأعلى مرتبة) || key ) وتحتوي على بيانات شخصية في الحمولة مباشرةً.
3. الأختام الزمنية RFC 3161
Section titled “3. الأختام الزمنية RFC 3161”في timestamps.tsv صف واحد لكل رمز:
<file.tsr> <kind> <stamped data> <provider> <level at issuance>البيانات المختومة سلسلة hex بأحرف صغيرة: proof_hash لسجل، أو جذر Merkle، أو package_hash. وقّعت هيئة الختم الزمني (TSA) قيمة SHA-256 لمحارف ASCII في تلك السلسلة، بلا سطر جديد في النهاية:
printf '%s' "<stamped data>" > data.txtopenssl 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.deropenssl cms -verify -inform DER -in token.der -CAfile <tsa_root.pem> \ -purpose timestampsign -binary -out tstinfo.deropenssl 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>)# خطوة واحدة، مثل "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 في الموافقة على المستندات والتوقيع)، ولا أن السجل أُنشئ بصورة صحيحة أصلًا. السجلات التي ليس لها رمز ختم زمني تعتمد على تجزئة الإثبات وحدها، ومن ثَم على سرية المفتاح.