סכמת החתימה של APK בגרסה 2 היא סכמת חתימה של קבצים מלאים, שמגבירה את מהירות האימות ומחזקת את האחריות על התקינות על ידי זיהוי שינויים בחלקים המוגנים של ה-APK.
לאחר חתימה באמצעות סכימת החתימה של APK בגרסה 2, היא מוסיפה בלוק חתימת APK לקובץ ה-APK מיד לפני הקטע ZIP Central Directory. בתוך הבלוק לחתימה על קובץ ה-APK, חתימות v2 ומידע על זהות החתום מאוחסנים בבלוק של סכמת חתימה על קובץ APK v2.
סכמת החתימה על 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 בגרסה 2 מאוחסן בתוך הבלוק של חתימת ה-APK במזהה 0x7109871a
.
הפורמט של הבלוק של סכמת החתימה על APK v2 הוא כדלקמן (כל הערכים המספריים הם little-endian, וכל השדות עם קידומת אורך משתמשים ב-uint32 לאורכו):
- רצף עם קידומת אורך של
signer
עם קידומת אורך:signed data
עם קידומת באורך :- רצף עם קידומת אורך של
digests
עם קידומת אורך:signature algorithm ID
(uint32)- (עם קידומת באורך)
digest
– תוכן מוגן בפני שינויים
- רצף עם קידומת באורך של X.509
certificates
:- X.509
certificate
עם קידומת באורך (ASN.1 DER form)
- X.509
- רצף עם קידומת אורך של
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 בייטים של salt, נגרר: 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
- ZIP End of Central Directory
סכמת החתימה על APKs v2 מגינה על השלמות של קטעים 1, 3, 4 ואת הבלוק signed data
של סכמת החתימה על APKs v2 שנמצא בתוך קטע 2.
תקינות החלקים 1, 3 ו-4 מוגנת על ידי סיכום אחד או יותר של התוכן שלהם, שנשמר בבלוק signed data
, שמוגן בתורו על ידי חתימה אחת או יותר.
התקציר של סעיפים 1, 3 ו-4 מחושב באופן הבא, בדומה לעץ מרקל דו-שלבי.
כל קטע מחולק למקטעים רצופים של 1MB (220 בייטים). יכול להיות שהקטע האחרון בכל קטע יהיה קצר יותר. הדיגסט של כל מקטע מחושב על סמך שרשור של הבייט 0xa5
, אורך המקטע בבייטים (uint32 ב-little-endian) ותוכן המקטע. הסיכום של הרמה העליונה מחושב על סמך שרשור של הבית 0x5a
, מספר הקטעים (uint32 ב-little-endian) ושרשור של סיכומי הקטעים בסדר שבו הקטעים מופיעים ב-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.
אימות של סכמת חתימה על 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 גרסה 2:- צריך לבחור את
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, וכל הרשומות חייבות להיות חתומות על ידי אותה קבוצה של חותמים. התקינות שלו מאומתת באופן הבא:
- כל חותם מיוצג באמצעות רשומת JAR /<signer>.SF ו-meta-INF/<signer>.(RSA|DSA|EC).
- <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.
- עבור כל רשומת JAR שמוגנת על ידי בדיקת תקינות, הקובץ META-INF/MANIFEST.MF מכיל קטע בעל שם תואם שמכיל את הסיכום של התוכן הלא דחוס של הרשומה. כל התקצירים האלה מאומתים.
- אימות ה-APK נכשל אם חבילת ה-APK מכילה רשומות JAR שלא מופיעות ב-MANIFEST.MF ולא חלק מחתימה JAR.
לכן, שרשרת ההגנה היא <signer>.(RSA|DSA|EC) -> <signer>.SF -> MANIFEST.MF -> התוכן של כל רשומת JAR שמוגנת על ידי בדיקת תקינות.