APK Signature Scheme v2 הוא סכימה לחתימה על קובץ שלם שמגבירה את מהירות האימות ומחזקת את ההתחייבויות לשלמות על ידי זיהוי שינויים בחלקים המוגנים של קובץ ה-APK.
כשחותמים באמצעות APK Signature Scheme v2, מוסיפים בלוק חתימה של APK לקובץ ה-APK, מיד לפני הקטע ZIP Central Directory. בתוך הבלוק לחתימה על קובץ ה-APK, חתימות v2 ומידע על זהות החתום מאוחסנים בבלוק של סכמת חתימה על קובץ APK v2.
איור 1. קובץ APK לפני ואחרי החתימה
סכמת החתימה על APKs v2 הוצגה ב-Android 7.0 (Nougat). כדי שאפשר יהיה להתקין קובץ APK במכשירי Android 6.0 (Marshmallow) ובמכשירים ישנים יותר, צריך לחתום על קובץ ה-APK באמצעות חתימה על JAR לפני החתימה עליו באמצעות הסכימה v2.
בלוק לחתימה על קובץ APK
כדי לשמור על תאימות לאחור לפורמט APK v1, חתימות APK בגרסה 2 ואילך מאוחסנות בתוך בלוק חתימת APK – קונטיינר חדש שהוצג כדי לתמוך בסכמת החתימה APK v2. בקובץ 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 End of Central Directory בסוף הקובץ, ואז קריאת הזזת ההתחלה של הספרייה המרכזית מהרשומה). הערך magic
מספק דרך מהירה לקבוע שהקטע שלפני Central Directory הוא ככל הנראה בלוק החתימה של קובץ ה-APK. לאחר מכן, הערך size of
block
מפנה ביעילות לתחילת הבלוק בקובץ.
כשמתרגמים את הבלוק, צריך להתעלם מזוגות מזהה-ערך עם מזהים לא ידועים.
חסימה של סכמת חתימה על APK v2
חבילת ה-APK חתומה על ידי חותם/זהות אחד או יותר, שכל אחד מהם מיוצג על ידי מפתח חתימה. המידע הזה מאוחסן כבלוק של APK Signature Scheme v2. לגבי כל חתום, נשמרים הפרטים הבאים:
- צמדי ערכים (signature algorithm, digest, signature). המסכם נשמר כדי לנתק את אימות החתימה מבדיקת התקינות של תוכן ה-APK.
- שרשרת אישורי X.509 שמייצגת את זהות החתום.
- מאפיינים נוספים כצמדי מפתח/ערך.
לכל חותם, חבילת ה-APK מאומתת באמצעות חתימה נתמכת מהרשימה שצוינה. המערכת מתעלמת מחתימות עם אלגוריתמים לא מוכרים של חתימות. בכל הטמעה, המערכת בוחרת את החתימה שבה משתמשים כשנמצאות כמה חתימות נתמכות. כך נוכל להציג בעתיד שיטות חתימה חזקות יותר באופן תואם לאחור. מומלץ לאמת את החתימה החזקה ביותר.
פורמט
הבלוק של סכמת החתימה על APK v2 מאוחסן בתוך הבלוק של חתימת ה-APK במזהה 0x7109871a
.
הפורמט של הבלוק של סכמת החתימה על APK v2 הוא כדלקמן (כל הערכים המספריים הם little-endian, וכל השדות עם קידומת אורך משתמשים ב-uint32 לאורכו):
- רצף עם קידומת אורך של
signer
עם קידומת אורך:signed data
עם קידומת באורך:- רצף עם קידומת אורך של
digests
עם קידומת אורך:signature algorithm ID
(uint32)- (עם קידומת באורך)
digest
– תוכן מוגן בפני פגיעה בשלמות
- רצף עם קידומת באורך של X.509
certificates
:certificate
של X.509 עם קידומת באורך (ASN.1 DER form)
- רצף עם קידומת אורך של
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. האפשרות הזו מיועדת למערכות build שדורשות חתימות גורליות.
- 0x0104 – RSASSA-PKCS1-v1_5 עם סיכום SHA2-512. האפשרות הזו מיועדת למערכות build שדורשות חתימות גורליות.
- 0x0201 – ECDSA עם סיכום SHA2-256
- 0x0202 – ECDSA עם סיכום SHA2-512
- 0x0301 – מודעות דינמיות לרשת החיפוש עם סיכום SHA2-256
פלטפורמת Android תומכת בכל אלגוריתמי החתימה שלמעלה. כלים לחתימה יכולים לתמוך בקבוצת משנה של האלגוריתמים.
הגדלים והעקומות של מפתחות EC נתמכים:
- RSA: 1024, 2048, 4096, 8192, 16384
- EC: NIST P-256, P-384, P-521
- DSA: 1024, 2048, 3072
תוכן שמוגן על ידי הגנה על תקינות האפליקציה
למטרות הגנה על תוכן ה-APK, חבילת APK מורכבת מארבעה קטעים:
- התוכן של רשומות ה-ZIP (מ-offset 0 ועד לתחילת הבלוק של חתימה על קובץ ה-APK)
- בלוק לחתימה על קובץ APK
- ZIP Central Directory
- ZIP End of Central Directory
איור 2. קטעים ב-APK אחרי החתימה
סכמת החתימה על APKs v2 מגינה על השלמות של קטעים 1, 3, 4 ואת הבלוק signed data
של סכמת החתימה על APKs v2 שנמצא בתוך הקטע 2.
תקינות החלקים 1, 3 ו-4 מוגנת על ידי סיכום אחד או יותר של התוכן שלהם, שנשמר בבלוק signed data
, שמוגן בתורו על ידי חתימה אחת או יותר.
הדיגסט של קטעים 1, 3 ו-4 מחושב באופן הבא, בדומה לעץ Merkle דו-שכבתי.
כל קטע מחולק למקטעים רצופים של 1MB (220 בייטים). יכול להיות שהקטע האחרון בכל קטע יהיה קצר יותר. הדיגסט של כל מקטע מחושב על סמך שרשור של הבייט 0xa5
, אורך המקטע בבייטים (uint32 ב-little-endian) ותוכן המקטע. הסיכום של הרמה העליונה מחושב על סמך שרשור של הבית 0x5a
, מספר הקטעים (uint32 ב-little-endian) ושרשור של סיכומי הקטעים בסדר שבו הקטעים מופיעים ב-APK. כדי לזרז את החישוב, ה-digest מחושב בקטעים.
איור 3. סיכום APK
ההגנה על הקטע 4 (ZIP End of Central Directory) מורכבת בגלל הקטע שמכיל את ההיסט של ZIP Central Directory. ההיסט משתנה כשהגודל של בלוק החתימה של ה-APK משתנה, למשל כשמוסיפים חתימה חדשה. לכן, כשמחשבים את הסיכום על סמך ZIP End of Central Directory, צריך להתייחס לשדה שמכיל את ה-offset של ZIP Central Directory כאל שדה שמכיל את ה-offset של בלוק החתימה של ה-APK.
אמצעי הגנה מפני רולבק
תוקף יכול לנסות לאמת קובץ APK בחתימה v2 כקובץ APK בחתימה v1 בפלטפורמות Android שתומכות באימות של קובץ APK בחתימה v2. כדי לצמצם את ההתקפה הזו, חבילות APK בחתימה v2 שגם חתומות בחתימה v1 חייבות לכלול את המאפיין X-Android-APK-Signed בקטע הראשי של קובצי META-INF/*.SF שלהן. הערך של המאפיין הוא קבוצה של מזהי סכמות חתימת APK שמופרדים בפסיקים (המזהה של הסכימה הזו הוא 2). כשמאמתים את החתימה של v1, מאמת ה-APK נדרש לדחות חבילות APK שאין להן חתימה של סכמת החתימה של ה-APK שמאמת ה-APK מעדיף מתוך הקבוצה הזו (למשל, סכמה v2). ההגנה הזו מבוססת על העובדה שתוכן של קובצי META-INF/*.SF מוגן בחתימות v1. ראו את הקטע אימות של APK בחתימה JAR.
תוקף יכול לנסות להסיר חתימות חזקות יותר מהבלוק של סכמת החתימה על APK v2. כדי לצמצם את התקפה הזו, רשימת המזהים של אלגוריתמי החתימה שבהם חתמתם על קובץ ה-APK מאוחסנת בבלוק signed data
שמוגן על ידי כל חתימה.
אימות
ב-Android 7.0 ואילך, אפשר לאמת קובצי APK לפי הסכימה לחתימה על קובצי APK מגרסה 2 ואילך או לפי חתימת JAR (הסכימה מגרסה 1). בפלטפורמות ישנות יותר מתעלמים מחתימות v2 ומאמתים רק חתימות v1.
איור 4. תהליך האימות של חתימת ה-APK (השלבים החדשים מסומנים באדום)
אימות של סכמת חתימה על APK v2
- מאתרים את הבלוק של חתימה על קובץ ה-APK ומוודאים:
- שני שדות גודל של בלוק החתימה של קובץ ה-APK מכילים את אותו ערך.
- אחרי הרשומה ZIP Central Directory מופיעה הרשומה ZIP End of Central Directory.
- אחרי ZIP End of Central Directory לא מופיעים נתונים נוספים.
- מאתרים את הבלוק הראשון של סכמת החתימה על APK v2 בתוך הבלוק של חתימת ה-APK. אם הבלוק v2 קיים, ממשיכים לשלב 3. אחרת, צריך לעבור לאימות ה-APK באמצעות סכמה v1.
- לכל
signer
בבלוק של סכמת החתימה על APK v2:- בוחרים את
signature algorithm ID
הנתמכת ביותר מ-signatures
. סדר החוזק נקבע לפי כל גרסה של הטמעה או פלטפורמה. - מוודאים שה-
signature
התואם מ-signatures
תואם ל-signed data
באמצעותpublic key
. (עכשיו אפשר לנתח אתsigned data
). - מוודאים שהרשימה הממוזערת של מזהי אלגוריתמי החתימה ב-
digests
וב-signatures
זהה. (המטרה היא למנוע הסרה או הוספה של חתימות). - מחשבים את הסיכום של תוכן ה-APK באמצעות אותו אלגוריתם סיכום שמשמש את אלגוריתם החתימה.
- מוודאים שה-digest המחושב זהה ל-
digest
התואם מ-digests
. - מוודאים ש-SubjectPublicKeyInfo של
certificate
הראשון ב-certificates
זהה ל-public key
.
- בוחרים את
- האימות יצליח אם נמצא לפחות
signer
אחד ושלב 3 הצליח לכלsigner
שנמצא.
הערה: אם מתרחשת כשל בשלב 3 או 4, אסור לאמת את ה-APK באמצעות הסכימה v1.
אימות APK בחתימה של JAR (סכמה v1)
קובץ ה-APK החתום ב-JAR הוא קובץ JAR חתום רגיל, שחייב להכיל בדיוק את הרשומות שמפורטות בקובץ META-INF/MANIFEST.MF, וכל הרשומות חייבות להיות חתומות על ידי אותה קבוצה של חותמים. התקינות שלו מאומתת באופן הבא:
- כל חתום מיוצג על ידי רשומה מסוג META-INF/<signer>.SF ו-META-INF/<signer>.(RSA|DSA|EC) JAR.
- <signer>.(RSA|DSA|EC) הוא קובץ PKCS #7 CMS ContentInfo עם מבנה SignedData שהחתימה שלו מאומתת באמצעות הקובץ <signer>.SF.
- קובץ <signer>.SF מכיל סיכום של הקובץ כולו של META-INF/MANIFEST.MF וסיכומים של כל קטע ב-META-INF/MANIFEST.MF. מתבצע אימות של הסיכום של כל הקובץ של MANIFEST.MF. אם האימות הזה נכשל, מתבצע אימות של הסיכום של כל קטע ב-MANIFEST.MF.
- הקובץ META-INF/MANIFEST.MF מכיל, לכל רשומת JAR שמוגנת על ידי בדיקת תקינות, קטע בעל שם תואם שמכיל את הסיכום של התוכן הלא דחוס של הרשומה. כל הסיכומים האלה מאומתים.
- אימות ה-APK נכשל אם חבילת ה-APK מכילה רשומות JAR שלא מופיעות ב-MANIFEST.MF ולא חלק מחתימה JAR.
לכן, שרשרת ההגנה היא <signer>.(RSA|DSA|EC) -> <signer>.SF -> MANIFEST.MF -> התוכן של כל רשומת JAR שמוגנת על ידי בדיקת תקינות.