החל מ-Android 12, מודול Android Runtime (ART) הוא מודול Mainline. יכול להיות שעדכון המודול ידרוש בנייה מחדש של ארטיפקטים של קומפילציה מראש (AOT) של קובצי JAR של bootclasspath ושל שרת המערכת. מכיוון שהארטיפקטים האלה רגישים מבחינת אבטחה, ב-Android 12 יש תכונה שנקראת חתימה במכשיר, שנועדה למנוע שינוי של הארטיפקטים האלה. בדף הזה מוסבר על ארכיטקטורת החתימה במכשיר ועל האינטראקציות שלה עם תכונות אבטחה אחרות ב-Android.
עיצוב ברמה גבוהה
חתימה במכשיר מורכבת משני רכיבי ליבה:
odrefresh
הוא חלק ממודול ART Mainline. הוא אחראי ליצירת פריטי מידע שנוצרו בתהליך פיתוח (Artifact) בזמן הריצה. הוא בודק את הארטיפקטים הקיימים מול הגרסה המותקנת של מודול ART, קובצי ה-JAR של bootclasspath וקובצי ה-JAR של שרת המערכת, כדי לקבוע אם הם עדכניים או אם צריך ליצור אותם מחדש. אם צריך ליצור אותם מחדש,odrefresh
יוצר אותם ומאחסן אותם.
odsign
הוא קובץ בינארי שמהווה חלק מפלטפורמת Android. הוא פועל במהלך האתחול המוקדם, מיד אחרי שהמחיצה/data
מותקנת. התפקיד העיקרי שלו הוא להפעיל אתodrefresh
כדי לבדוק אם צריך ליצור או לעדכן ארטיפקטים. לכל ארטיפקט חדש או מעודכן שנוצר על ידיodrefresh
odsign
, מחושבת פונקציית גיבוב (hash). התוצאה של חישוב הגיבוב נקראת תקציר קובץ. לגבי כל הארטיפקטים שכבר קיימים,odsign
מוודא שהערכים של ה-digests של הארטיפקטים הקיימים תואמים לערכים של ה-digests ש-odsign
חישב בעבר. כך אפשר לוודא שלא נעשה שינוי בארטיפקטים.
במקרים של שגיאות, למשל כשערך ה-digest של קובץ לא תואם, הפקודות odrefresh
ו-odsign
מוחקות את כל הארטיפקטים הקיימים ב-/data
ומנסות ליצור אותם מחדש. אם הפעולה הזו נכשלת, המערכת חוזרת למצב JIT.
האפליקציות odrefresh
ו-odsign
מוגנות על ידי dm-verity
, והן חלק משרשרת ההפעלה המאומתת של Android.
חישוב של תמציות קבצים באמצעות fs-verity
fs-verity היא תכונה של ליבת Linux שמבצעת אימות של נתוני קבצים על בסיס עץ מרקל. הפעלת fs-verity בקובץ גורמת למערכת הקבצים ליצור עץ מרקל על נתוני הקובץ באמצעות גיבוב SHA-256, לאחסן אותו במיקום מוסתר לצד הקובץ ולסמן את הקובץ כקובץ לקריאה בלבד. fs-verity מאמת באופן אוטומטי את נתוני הקובץ מול עץ מרקל לפי דרישה בזמן הקריאה. fs-verity מאפשר גישה לגיבוב הבסיסי של עץ מרקל כערך שנקרא fs-verity file digest, ו-fs-verity מוודא שכל נתון שנקרא מהקובץ עקבי עם הגיבוב הזה.
odsign
משתמש ב-fs-verity כדי לשפר את ביצועי האתחול על ידי אופטימיזציה של האימות הקריפטוגרפי של ארטיפקטים שנערכו במכשיר בזמן האתחול. כשנוצר ארטיפקט, odsign
מפעיל עליו את fs-verity. כש-odsign
מאמת ארטיפקט, הוא מאמת את תקציר הקובץ של fs-verity במקום את הגיבוב המלא של הקובץ. כך לא צריך לקרוא ולבצע גיבוב של כל הנתונים של הארטיפקט בזמן האתחול. במקום זאת, נתוני הארטיפקט עוברים גיבוב לפי דרישה על ידי fs-verity בזמן השימוש בהם, על בסיס בלוק אחר בלוק.
במכשירים שהקרנל שלהם לא תומך ב-fs-verity, odsign
חוזר לחישוב של תמציות קבצים במרחב המשתמש. odsign
משתמש באותו אלגוריתם גיבוב (hash) שמבוסס על עץ מרקל כמו fs-verity, כך שהתקצירים זהים בשני המקרים. נדרש fs-verity בכל המכשירים שהושקו עם Android מגרסה 11 ואילך.
אחסון של תמציות הקבצים
odsign
שומר את תמציות הקבצים של הארטיפקטים בקובץ נפרד בשם odsign.info
. כדי לוודא שלא בוצעו שינויים ב-odsign.info
, הוא חתום באמצעות מפתח חתימה עם מאפייני אבטחה חשובים.odsign.info
בפרט, אפשר ליצור את המפתח ולהשתמש בו רק במהלך האתחול הראשוני, שבמהלכו מופעל רק קוד מהימן. פרטים נוספים זמינים במאמר בנושא מפתחות חתימה מהימנים.
אימות של תמציות קבצים
בכל אתחול, אם odrefresh
קובע שהארטיפקטים הקיימים עדכניים, odsign
מוודא שלא בוצעו שינויים בקבצים מאז שהם נוצרו. הפקודה odsign
עושה זאת על ידי אימות של תמציות הקבצים. קודם כול, המערכת מאמתת את החתימה של odsign.info
. אם החתימה תקפה, odsign
מוודא שהתקציר של כל קובץ תואם לתקציר המתאים ב-odsign.info
.
מפתחות חתימה מהימנים
ב-Android 12 מוצגת תכונה חדשה של Keystore שנקראת 'מפתחות של שלב האתחול', שמטפלת בבעיות האבטחה הבאות:
- מה מונע מהתוקף להשתמש במפתח החתימה שלנו כדי לחתום על גרסה משלו של
odsign.info
? - מה מונע מתוקף ליצור מפתח חתימה משלו ולהשתמש בו כדי לחתום על גרסה משלו של
odsign.info
?
מפתחות של שלב האתחול מפצלים את מחזור האתחול של Android לרמות, וקושרים באופן קריפטוגרפי את היצירה והשימוש במפתח לרמה ספציפית. odsign
יוצר את מפתח החתימה שלו ברמה מוקדמת, כשרץ רק קוד מהימן, ומוגן באמצעות dm-verity
.
רמות שלבי האתחול ממוספרות מ-0 עד המספר הקסום 1000000000. במהלך תהליך האתחול של Android, אפשר להגדיל את רמת האתחול על ידי הגדרת מאפיין מערכת מ-init.rc
. לדוגמה, הקוד הבא מגדיר את רמת האתחול ל-10:
setprop keystore.boot_level 10
לקוחות של Keystore יכולים ליצור מפתחות שקשורים לרמת אתחול מסוימת. לדוגמה, אם יוצרים מפתח לרמת אתחול 10, אפשר להשתמש במפתח הזה רק כשהמכשיר נמצא ברמת אתחול 10.
odsign
משתמש ברמת אתחול 30, ומפתח החתימה שהוא יוצר קשור לרמת האתחול הזו. לפני שמשתמשים במפתח לחתימה על ארטיפקטים, odsign
מוודא שהמפתח משויך לרמת אתחול 30.
כך נמנעות שתי המתקפות שתוארו קודם בסעיף הזה:
- תוקפים לא יכולים להשתמש במפתח שנוצר, כי עד שהתוקף מקבל הזדמנות להריץ קוד זדוני, רמת האתחול עולה מעל 30, ומאגר המפתחות מסרב לבצע פעולות שמשתמשות במפתח.
- התוקפים לא יכולים ליצור מפתח חדש, כי עד שהם מקבלים הזדמנות להריץ קוד זדוני, רמת האתחול עולה מעל 30, ומאגר המפתחות מסרב ליצור מפתח חדש עם רמת האתחול הזו. אם תוקף יוצר מפתח חדש שלא משויך לרמת אתחול 30, מערכת
odsign
דוחה אותו.
מאגר המפתחות מוודא שאכיפת רמת האתחול מתבצעת בצורה תקינה. בקטעים הבאים מוסבר בפירוט איך עושים את זה בגרסאות שונות של KeyMint (לשעבר Keymaster).
הטמעה של Keymaster 4.0
גרסאות שונות של Keymaster מטפלות בהטמעה של מפתחות בשלב האתחול באופן שונה. במכשירים עם TEE/StrongBox בגרסה Keymaster 4.0, המערכת מטפלת בהטמעה באופן הבא:
- באתחול הראשון, Keystore יוצר מפתח סימטרי K0 עם התג
MAX_USES_PER_BOOT
שמוגדר לערך1
. כלומר, אפשר להשתמש במפתח רק פעם אחת בכל הפעלה. - במהלך האתחול, אם רמת האתחול עולה, אפשר ליצור מ-K0 מפתח חדש לרמת האתחול הזו באמצעות פונקציית HKDF:
Ki+i=HKDF(Ki, "some_fixed_string")
. לדוגמה, אם עוברים מרמת אתחול 0 לרמת אתחול 10, הפונקציה HKDF מופעלת 10 פעמים כדי לגזור את K10 מ-K0. כשמשנים את רמת האתחול, המפתח של רמת האתחול הקודמת נמחק מהזיכרון, והמפתחות שמשויכים לרמות אתחול קודמות כבר לא זמינים.
המקש K0 הוא מקש
MAX_USES_PER_BOOT=1
. המשמעות היא שאי אפשר להשתמש במפתח הזה מאוחר יותר במהלך האתחול, כי תמיד מתרחש לפחות מעבר אחד ברמת האתחול (לרמת האתחול הסופית).
כשלקוח Keystore כמו odsign
מבקש ליצור מפתח ברמת האתחול i
, ה-blob שלו מוצפן באמצעות מפתח Ki
. מכיוון שהתכונה Ki
לא זמינה אחרי רמת האתחול i
, אי אפשר ליצור את המפתח הזה או לפענח אותו בשלבי אתחול מאוחרים יותר.
הטמעה של Keymaster 4.1 ו-KeyMint 1.0
ההטמעות של Keymaster 4.1 ו-KeyMint 1.0 דומות מאוד להטמעה של Keymaster 4.0. ההבדל העיקרי הוא ש-K0 הוא לא מפתח MAX_USES_PER_BOOT
, אלא מפתח EARLY_BOOT_ONLY
, שהוצג ב-Keymaster 4.1. אפשר להשתמש במפתח EARLY_BOOT_ONLY
רק בשלבים הראשונים של האתחול, כשלא מופעל קוד לא מהימן. ההגנה הזו מספקת רמה נוספת של אבטחה: בהטמעה של Keymaster 4.0, תוקף שפורץ למערכת הקבצים ול-SELinux יכול לשנות את מסד הנתונים של Keystore כדי ליצור מפתח MAX_USES_PER_BOOT=1
משלו לחתימה על ארטיפקטים. מתקפה כזו לא אפשרית ביישומים של Keymaster 4.1 ו-KeyMint 1.0, כי אפשר ליצור מפתחות EARLY_BOOT_ONLY
רק במהלך אתחול מוקדם.
הרכיב הציבורי של מפתחות חתימה מהימנים
odsign
מאחזר את רכיב המפתח הציבורי של מפתח החתימה מ-Keystore.
עם זאת, Keystore לא מאחזר את המפתח הציבורי הזה מה-TEE/SE שמחזיק במפתח הפרטי התואם. במקום זאת, הוא מאחזר את המפתח הציבורי ממסד הנתונים שלו בדיסק. המשמעות היא שתוקף שפרץ למערכת הקבצים יכול לשנות את מסד הנתונים של מאגר המפתחות כך שיכיל מפתח ציבורי שהוא חלק מזוג מפתחות ציבורי/פרטי שנמצא בשליטתו.
כדי למנוע את המתקפה הזו, odsign
יוצר מפתח HMAC נוסף עם אותה רמת אתחול כמו מפתח החתימה. לאחר מכן, כשיוצרים את מפתח החתימה, odsign
משתמש במפתח ה-HMAC הזה כדי ליצור חתימה של המפתח הציבורי, ושומר אותה בדיסק. באיתחולים הבאים, כשמאחזרים את המפתח הציבורי של מפתח החתימה, המערכת משתמשת במפתח ה-HMAC כדי לוודא שהחתימה בדיסק תואמת לחתימה של המפתח הציבורי שאוחזר. אם הם זהים, המפתח הציבורי מהימן, כי אפשר להשתמש במפתח ה-HMAC רק ברמות אתחול מוקדמות, ולכן לא יכול להיות שהוא נוצר על ידי תוקף.