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

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

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

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

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

تم طرح الإصدار 2 من مخطّط توقيع حِزم APK في الإصدار 7.0 من نظام التشغيل Android (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 طريقة سريعة للتأكّد من أنّ ما يسبق الدليل المركزي هو على الأرجح مجموعة توقيع 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 على النحو التالي، على غرار شجرة Merkle ذات المستويَين. يتم تقسيم كل قسم إلى أجزاء متتالية بحجم 1 ميغابايت (220 بايت). قد يكون الجزء الأخير في كل قسم أقصر. يتم احتساب ملخّص كل قطعة من خلال تسلسل البايت 0xa5 وطول القطعة بالبايت (الترتيب الأقل أهمية لوحدة 32 بت) ومحتوى القطعة. يتم احتساب الملخّص على مستوى الملف بالكامل من خلال تسلسل البايت 0x5a وعدد الأجزاء (بترتيب endian الأصغر) وتسلسل الملخّصات للأجزاء في الترتيب الذي تظهر به الأجزاء في حزمة APK. يتم احتساب الملخّص بطريقة مجزّأة ل السماح بتسريع عملية الحساب من خلال إجراءها بشكل موازٍ.

خلاصة حزمة APK

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

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

وسائل الحماية من الرجوع إلى الحالة السابقة

يمكن للمهاجم محاولة إثبات صحة حزمة APK موقَّعة بالإصدار 2 على أنّها حزمة APK موقَّعة بالإصدار 1 على منصّات 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 الذي يتم حمايته بواسطة كل توقيع.

إثبات الملكية

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

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

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

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

  1. حدِّد موقع مجموعة توقيع APK وتأكَّد مما يلي:
    1. يحتوي حقلَا الحجم الخاصان بوحدة توقيع APK على القيمة نفسها.
    2. يتبع "الدليل المركزي" في ملف ZIP على الفور سجلّ "نهاية الدليل المركزي" في ملف ZIP.
    3. لا يتبع رمز ZIP الخاص بنهاية الدليل المركزي المزيد من البيانات.
  2. حدِّد موقع أول مجموعة من الإصدار 2 من مخطّط توقيع حزمة APK داخل مجموعة توقيع حزمة APK. إذا كان هناك قالب v2، انتقِل إلى الخطوة 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.
  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 محمي بسلامة البيانات.