החל מ-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כדי לבדוק אם צריך ליצור או לעדכן ארטיפקטים. לכל ארטיפקט חדש או מעודכן שנוצר על ידיodrefreshodsign, מחושבת פונקציית גיבוב (hash). התוצאה של חישוב הגיבוב נקראת תקציר קובץ. לגבי כל הארטיפקטים שכבר קיימים,odsignמוודא שהערכים של ה-digest של הארטיפקטים הקיימים תואמים לערכים של ה-digest ש-odsignחישב בעבר. כך אפשר לוודא שלא נעשה שינוי בארטיפקטים.
במקרים של שגיאות, למשל אם הגיבוב של קובץ לא תואם, הפונקציות 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 משתמש באותו אלגוריתם גיבוב מבוסס-עץ מרקל כמו fs-verity, כך שהתקצירים זהים בשני המקרים. fs-verity נדרש בכל המכשירים שהושקו עם Android מגרסה 11 ואילך.
אחסון של תקצירי הקבצים
odsign מאחסן את תמציות הקבצים של הארטיפקטים בקובץ נפרד בשם
odsign.info. כדי לוודא שלא בוצעו שינויים ב-odsign.info, הוא חתום באמצעות מפתח חתימה עם מאפייני אבטחה חשובים.odsign.info בפרט, אפשר ליצור את המפתח ולהשתמש בו רק במהלך האתחול הראשוני, ובשלב הזה פועל רק קוד מהימן. פרטים נוספים זמינים במאמר בנושא מפתחות חתימה מהימנים.
אימות של תמציות קבצים
בכל אתחול, אם odrefresh קובעת שהארטיפקטים הקיימים עדכניים, odsign מוודאת שלא בוצעו שינויים בקבצים מאז שהם נוצרו. הפקודה odsign עושה זאת על ידי אימות של תקצירי הקבצים. קודם כול, המערכת מאמתת את החתימה של odsign.info. אם החתימה תקפה, odsign מוודא שהתקציר של כל קובץ תואם לתקציר המתאים ב-odsign.info.
מפתחות חתימה מהימנים
ב-Android 12 הושקה תכונה חדשה של Keystore שנקראת boot stage keys (מפתחות של שלב האתחול), שנועדה לטפל בבעיות האבטחה הבאות:
- מה מונע מתוקף להשתמש במפתח החתימה שלנו כדי לחתום על גרסה משלו של
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 מטפלות בהטמעה של מפתחות בשלב האתחול בצורה שונה. במכשירים עם Keymaster 4.0 TEE/StrongBox, Keymaster מטפל בהטמעה באופן הבא:
- באתחול הראשון, 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 רק ברמות אתחול מוקדמות, ולכן לא יכול להיות שהוא נוצר על ידי תוקף.