الإصدار 2 من مخطّط توقيع حزمة APK

الإصدار 2 من مخطّط توقيع حزمة APK هو مخطّط لتوقيع الملف بالكامل يزيد من سرعة التحقّق ويعزّز ضمانات السلامة من خلال رصد أي تغييرات في الأجزاء المحمية من حزمة APK.

يؤدي التوقيع باستخدام الإصدار 2 من نظام توقيع APK إلى إدراج كتلة توقيع حِزم APK في ملف APK مباشرةً قبل قسم "دليل ZIP المركزي". داخل حزمة توقيع حزمة APK، يتم تخزين توقيعات الإصدار 2 ومعلومات هوية الموقِّع في الجزء 2 من مخطّط توقيع حِزم APK.

ملف APK قبل التوقيع وبعده

الشكل 1. ملف APK قبل التوقيع وبعده

تم تقديم الإصدار 2 من مخطّط توقيع حزمة APK في نظام التشغيل Android 7.0 (Nougat). لجعل ملف APK قابلاً للتثبيت على أجهزة Android 6.0 (Marshmallow) والإصدارات الأقدم، يجب توقيع ملف APK باستخدام توقيع JAR قبل توقيعه باستخدام مخطّط الإصدار 2.

حظر توقيع حِزم APK

للحفاظ على التوافق مع الإصدارات القديمة من تنسيق حِزم APK، يتم تخزين توقيعات الإصدار 2 من حِزم APK والإصدارات الأحدث داخل كتلة توقيع حِزم APK، وهي حاوية جديدة تم طرحها لتوفير توافق مع الإصدار 2 من مخطّط توقيع حِزم APK. في ملف APK، تقع كتلة توقيع APK قبل الدليل المركزي لملف ZIP مباشرةً، والذي يقع في نهاية الملف.

يحتوي المربّع على أزواج من المعرّفات والقيم مُغلفة بطريقة تسهّل العثور على المربّع في حزمة APK. يتم تخزين توقيع الإصدار 2 من حزمة APK كزوج قيمة / معرّف يحمل المعرّف 0x7109871a.

التنسيق

تنسيق كتلة توقيع APK هو على النحو التالي (جميع الحقول الرقمية بالتنسيق little-endian):

  • size of block بايت (باستثناء هذا الحقل) (uint64)
  • تسلسل أزواج قيمة معرّف مسبوقة بطول uint64:
    • ID (uint32)
    • value (طول متغيّر: طول الزوج - 4 بايت)
  • size of block بايت، وهو نفسه الحقل الأول (uint64)
  • magic "APK Sig Block 42" (16 بايت)

يتم تحليل حِزمة APK من خلال العثور أولاً على بداية "الدليل المركزي" لملف ZIP (من خلال العثور على سجلّ "نهاية الدليل المركزي" لملف ZIP في نهاية الملف، ثم قراءة الموضع النسبي لبدء "الدليل المركزي" من السجلّ). توفّر قيمة magic طريقة سريعة للتأكّد من أنّ ما يسبق Central Directory هو على الأرجح مجموعة توقيع APK. تشير قيمة size of block بعد ذلك بفعالية إلى بداية الكتلة في الملف.

يجب تجاهل أزواج القيمة والمعرّف التي تحتوي على معرّفات غير معروفة عند تفسير الوحدة.

حظر الإصدار 2 من مخطّط توقيع حِزم APK

يتم توقيع حِزم APK بواسطة موقِّع أو هوية واحدة أو أكثر، ويمثّل كلّ منها مفتاح توقيع. ويتم تخزين هذه المعلومات كتكتُل من الإصدار 2 من مخطّط توقيع حزمة APK. بالنسبة إلى كل ملف شخصي للموقّع، يتم تخزين المعلومات التالية:

  • (خوارزمية التوقيع، الملخص، التوقيع) الصفوف. ويتم تخزين الملخّص لفصل عملية التحقّق من التوقيع عن التحقّق من سلامة محتوى حزمة APK.
  • سلسلة شهادة X.509 التي تمثّل هوية الموقّع
  • سمات إضافية كأزواج المفتاح/القيمة

بالنسبة إلى كل موقّع، يتم التحقّق من حزمة APK باستخدام توقيع متوافق من القائمة المقدَّمة. يتم تجاهل التوقيعات التي تتضمّن خوارزميات توقيع غير معروفة. ويعود اختيار التوقيع المراد استخدامه عند العثور على عدة توقيعات متوافقة إلى كل عملية تنفيذ. يتيح ذلك تقديم طرق توقيع أكثر أمانًا في المستقبل بطريقة متوافقة مع الإصدارات القديمة. ينُصَح بالتحقق من صحة التوقيع الأقوى.

التنسيق

يتم تخزين الإصدار 2 من مخطّط توقيع حزمة APK داخل كتلة توقيع حزمة APK ضمن المعرّف 0x7109871a.

تنسيق حِزمة نظام توقيع APK الإصدار 2 هو كما يلي (جميع القيم الرقمية تكون بالتنسيق little-endian، وجميع الحقول التي تسبقها قيمة الطول تستخدم uint32 لتحديد الطول):

  • تسلسل مسبوق بطول من signer مسبوق بطول:
    • signed data التي تحتوي على بادئة الطول:
      • تسلسل مسبوق بطول من digests مسبوق بطول:
      • تسلسل مسبوق بطول X.509 certificates:
        • certificate‏X.509 مسبوقة بطول certificate (تنسيق ASN.1 DER)
      • تسلسل مسبوق بطول من additional attributes مسبوق بطول:
        • ID (uint32)
        • value (طول متغيّر: طول السمة الإضافية - 4 بايت)
    • تسلسل مسبوق بطول من signatures مسبوق بطول:
      • signature algorithm ID (uint32)
      • signature مسبوقة بطولها على signed data
    • public key مسبوقة بالطول (SubjectPublicKeyInfo، نموذج ASN.1 DER)

أرقام تعريف خوارزميات التوقيع

  • ‫0x0101:‏ RSASSA-PSS مع تجزئة SHA2-256 وSHA2-256 MGF1 و32 بايت من الملح، اللاحقة: 0xbc
  • 0x0102—RSASSA-PSS مع ملخص SHA2-512، SHA2-512 MGF1، 64 بايت من الملح، المقطورة: 0xbc
  • 0x0103—RSASSA-PKCS1-v1_5 مع ملخص SHA2-256. يُستخدم هذا الإجراء لأنظمة الإنشاء التي تتطلّب استخدام توقيعات محدّدة.
  • 0x0104—RSASSA-PKCS1-v1_5 مع ملخص SHA2-512. يُستخدم هذا الإجراء لأنظمة الإنشاء التي تتطلّب استخدام توقيعات محدّدة.
  • 0x0201: ECDSA مع خلاصة SHA2-256
  • 0x0202: ECDSA مع خلاصة SHA2-512
  • 0x0301: توقيع رقمي متقدّم (DSA) مع خلاصة SHA2-256

جميع خوارزميات التوقيع المذكورة أعلاه متوافقة مع نظام Android الأساسي. يمكن أن تتيح أدوات التوقيع مجموعة فرعية من الخوارزميات.

أحجام المفاتيح ومنحنيات الإحالة الناجحة (EC) المتوافقة:

  • ‫RSA: 1024 و2048 و4096 و8192 و16384
  • التشفير المتماثل: NIST P-256 وP-384 وP-521
  • الإعلانات الديناميكية على شبكة البحث: 1024 و2048 و3072

المحتوى المحميّ بسلامة التطبيق

لأغراض حماية محتوى حِزم APK، تتألف حزمة APK من أربعة أقسام:

  1. محتوى إدخالات ZIP (من الموضع 0 حتى بداية مجموعة توقيع APK)
  2. حظر توقيع حِزم APK
  3. الدليل المركزي بتنسيق ZIP
  4. نهاية الدليل المركزي بتنسيق ZIP

أقسام حِزم APK بعد التوقيع

الشكل 2: أقسام حِزم APK بعد التوقيع

يحمي الإصدار 2 من مخطّط توقيع حزمة APK سلامة الأقسام 1 و3 و4 وكتلة signed data من الإصدار 2 من مخطّط توقيع حزمة APK المضمّنة داخل القسم 2.

يتم حماية سلامة الأقسام 1 و3 و4 من خلال ملخّص واحد أو أكثر من محتوياتها المخزّنة في وحدات signed data التي يتم بدورها حمايتها من خلال توقيع واحد أو أكثر.

يتم احتساب الملخّص في الأقسام 1 و3 و4 على النحو التالي، على غرار شجرة ميركل ذات المستويَين. يتم تقسيم كل قسم إلى أجزاء متتالية بحجم 1 ميغابايت (220 بايت). قد يكون الجزء الأخير في كل قسم أقصر. يتم احتساب ملخّص كل قطعة من خلال تسلسل البايت 0xa5 وطول القطعة بالبايت (الترتيب الأقل أهمية لوحدة 32 بت) ومحتويات القطعة. يتم احتساب الملخّص على مستوى الملف بالكامل من خلال تسلسل البايت 0x5a وعدد الأجزاء (بترتيب endian الأصغر) وتسلسل الملخّصات للأجزاء في الترتيب الذي تظهر به الأجزاء في حزمة APK. يتم احتساب الملخّص بطريقة مجزّأة ل السماح بتسريع عملية الحساب من خلال إجراءها بشكل موازٍ.

ملخص APK

الشكل 3: خلاصة حزمة APK

تصبح حماية القسم 4 (نهاية الدليل المركزي في ZIP) معقّدة بسبب القسم الذي يحتوي على الإزاحة للدليل المركزي في ZIP. يتغيّر البدء عند تغيُّر حجم كتلة توقيع حزمة APK، على سبيل المثال، عند إضافة توقيع جديد. وبالتالي، عند احتساب الملخّص على "نهاية دليل ZIP المركزي"، يجب التعامل مع الحقل الذي يحتوي على الإزاحة في دليل ZIP المركزي على أنّه يحتوي على الإزاحة في كتلة توقيع APK.

العودة إلى الإصدار السابق من إجراءات الحماية

قد يحاول أي مهاجم محاولة إثبات ملكية حِزمة APK موقَّعة من الإصدار v2 كحزمة APK موقَّعة بالإصدار v1 على أنظمة Android الأساسية التي تتيح التحقّق من حِزمة APK الموقَّعة بالإصدار 2. للحدّ من هذا الهجوم، يجب أن تحتوي حِزم APK الموقَّعة بالإصدار 2 والتي تم توقيعها أيضًا بالإصدار 1 على سمة X-Android-APK-Signed في القسم الرئيسي من ملفات META-INF/*.SF. قيمة سمة هي مجموعة مفصولة بفواصل من معرّفات مخطّط توقيع حزمة APK (معرّف هذا المخطّط هو 2). عند التحقّق من توقيع الإصدار 1، على أداة التحقّق من حِزم APK رفض حِزم APK التي لا تحتوي على توقيع لمخطّط توقيع حِزم APK الذي يفضّله أداة التحقّق من هذه المجموعة (مثل مخطّط الإصدار 2). تعتمد هذه الحماية على حقيقة أنّ محتوى ملفات META-INF/*.SF محمي بتوقيعات الإصدار 1. اطّلِع على القسم المعني بموضوع التحقّق من حِزم APK الموقَّعة باستخدام JAR.

يمكن للمهاجم محاولة إزالة التوقيعات الأقوى من حزمة توقيع APK باستخدام مخطط الإصدار 2. وللحدّ من حدة هذا الهجوم، يتم تخزين قائمة بأرقام تعريف خوارزميات التوقيع التي تم توقيع حزمة APK معها في كتلة "signed data" المحمية بكل توقيع.

التحقق

في الإصدار Android 7.0 والإصدارات الأحدث، يمكن التحقق من حِزم APK وفقًا للإصدار 2+ من مخطَّط توقيع حزمة APK أو توقيع JAR (مخطط الإصدار 1). تتجاهل الأنظمة الأساسية القديمة توقيعات الإصدار 2 وتتحقق من توقيعات الإصدار 1 فقط.

عملية التحقّق من توقيع حزمة APK

الشكل 4. عملية التحقّق من توقيع حزمة APK (الخطوات الجديدة باللون الأحمر)

التحقّق من الإصدار 2 من مخطّط توقيع حِزم APK

  1. حدِّد موقع مجموعة توقيع APK وتأكَّد مما يلي:
    1. يحتوي حقلَا الحجم الخاصان بوحدة توقيع APK على القيمة نفسها.
    2. يتبع "الدليل المركزي" في ملف ZIP على الفور سجلّ "نهاية الدليل المركزي" في ملف ZIP.
    3. لا يتبع رمز ZIP الخاص بنهاية الدليل المركزي المزيد من البيانات.
  2. حدِّد موقع أول مجموعة من الإصدار 2 من مخطّط توقيع حزمة APK داخل مجموعة توقيع حزمة APK. في حال توفُّر كتلة الإصدار 2، انتقِل إلى الخطوة 3. وإذا لم يكن كذلك، يمكنك الرجوع للتحقق من حزمة APK باستخدام مخطط الإصدار 1.
  3. لكل signer في مجموعة الإصدار 2 من مخطّط توقيع حِزم APK:
    1. اختَر signature algorithm ID الأقوى المتاح من signatures. يعتمد ترتيب مدى القوة على كل إصدار من إصدارات نظام التشغيل أو عملية التنفيذ.
    2. تحقَّق من signature المطابق من signatures مقارنةً بـ signed data باستخدام public key. (يمكن الآن تحليل signed data بأمان).
    3. تأكَّد من تطابق القائمة المرتبة لأرقام تعريف خوارزمية التوقيع في digests وsignatures. (يهدف ذلك إلى منع إزالة التوقيع أو إضافته).
    4. احسِب ملخص محتوى حزمة APK باستخدام خوارزمية الملخّص نفسها التي تستخدمها خوارزمية الملخص التي تستخدمها خوارزمية التوقيع.
    5. تأكَّد من أنّ الملخّص المحسوب مطابق لملف العميل المعني digest من digests.
    6. تأكَّد من أنّ SubjectPublicKeyInfo لأول certificate من certificates مطابقة لـ public key.
  4. تنجح عملية إثبات الملكية إذا تم العثور على سمة signer واحدة على الأقل ونجحت الخطوة 3 لكل منها في العثور على signer.

ملاحظة: يجب عدم التحقّق من حزمة APK باستخدام مخطط الإصدار 1 في حال حدوث خطأ في الخطوة 3 أو 4.

التحقّق من حِزم APK الموقَّعة باستخدام JAR (مخطّط الإصدار 1)

حزمة APK الموقَّعة باستخدام JAR هي حزمة JAR قياسية موقَّعة، ويجب أن تحتوي على الإدخالات المدرَجة فيملف META-INF/MANIFEST.MF بالضبط، ويجب أن تكون جميع الإدخالات موقَّعة من خلال المجموعة نفسها من الموقِّعين. يتم التحقّق من سلامتها على النحو التالي:

  1. يتم تمثيل كل موقّع من خلال إدخال META-INF/<signer>.SF و META-INF/<signer>.(RSA|DSA|EC) JAR.
  2. <signer>.(RSA|DSA|EC) هو PKCS‏ #7 CMS ContentInfo مع بنية SignedData يتم التحقّق من توقيعه على <signer>.SF file.
  3. يحتوي ملف ‎<signer>.SF على ملخّص للملف بأكمله من META-INF/MANIFEST.MF وملخّصات لكل قسم من META-INF/MANIFEST.MF. يتم التحقّق من خلاصة الملف بالكامل في MANIFEST.MF. وإذا تعذّر ذلك، يتم التحقّق من خلاصة كل قسم من أقسام MANIFEST.MF بدلاً من ذلك.
  4. يحتوي meta-INF/MANIFEST.MF على قسم ذي اسم مطابق، لكل إدخال في JAR محميًا بسلامة، يحتوي على ملخص لمحتوى الإدخال غير المضغوط. تم التحقّق من جميع هذه الملخّصات.
  5. يتعذّر إثبات صحة حزمة APK إذا كانت تحتوي على إدخالات JAR غير مُدرَجة في MANIFEST.MF وليست جزءًا من توقيع JAR.

وبالتالي، تكون سلسلة الحماية هي <signer>.(RSA|DSA|EC) -> <signer>.SF -> MANIFEST.MF -> محتوى كل إدخال JAR محمي بسلامة البيانات.