חתימה על גרסאות build לגרסה

בתמונות של Android OS נעשה שימוש בחתימות קריפטוגרפיות בשני מקומות:

  1. כל קובץ .apk בתוך התמונה חייב להיות חתום. מנהל החבילות של Android משתמש בחתימה .apk בשתי דרכים:
    • כשמחליפים אפליקציה, צריך לחתום עליה באותו מפתח שבו חתומה האפליקציה הישנה כדי לקבל גישה לנתונים של האפליקציה הישנה. זה נכון גם לעדכון אפליקציות משתמש על ידי החלפת .apk, וגם להחלפת אפליקציית מערכת בגרסה חדשה יותר שמותקנת ב-/data.
    • אם שתי אפליקציות או יותר רוצות לשתף מזהה משתמש (כדי שיוכלו לשתף נתונים וכו'), הן צריכות להיות חתומות באותו מפתח.
  2. חבילות העדכון ב-OTA חייבות להיות חתומות על ידי אחד מהמפתחות שהמערכת מצפה להם, אחרת תהליך ההתקנה ידחה אותן.

מפתחות גרסאות

העץ של Android כולל את test-keys בקטע build/target/product/security. כשיוצרים קובץ אימג' של Android OS באמצעות make, כל הקבצים של .apk חותמים באמצעות test-keys. מכיוון שמפתחות הבדיקה ידועים לציבור, כל אחד יכול לחתום על קובצי .APK משלו עם אותם מפתחות, דבר שעלול לאפשר להם להחליף או לפרוץ אפליקציות מערכת שמובנות בקובץ האימג' של מערכת ההפעלה. לכן חשוב מאוד לחתום על כל קובץ אימג' של מערכת Android OS שפורסם או נפרס באופן ציבורי באמצעות קבוצה מיוחדת של מפתחות הפצה שיש לכם גישה אליה בלבד.

כדי ליצור קבוצה ייחודית משלכם של מפתחות release, מריצים את הפקודות הבאות מהשורש של עץ Android:

subject='/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
mkdir ~/.android-certs
for x in releasekey platform shared media networkstack; do \
    ./development/tools/make_key ~/.android-certs/$x "$subject"; \
  done

צריך לשנות את $subject כך שישקף את הפרטים של הארגון. אפשר להשתמש בכל ספרייה, אבל חשוב לבחור מיקום מאובטח שגובה. ספקים מסוימים בוחרים להצפין את המפתח הפרטי שלהם באמצעות ביטוי סיסמה חזק ולאחסן את המפתח המוצפן במערכת בקרת הגרסאות. ספקים אחרים שומרים את מפתחות השחרור שלהם במקום אחר לגמרי, למשל במחשב עם הפרדה פיזית (air-gap).

כדי ליצור קובץ אימג' של פריט תוכן, צריך להשתמש ב:

make dist
sign_target_files_apks \
-o \    # explained in the next section
--default_key_mappings ~/.android-certs out/dist/*-target_files-*.zip \
signed-target_files.zip

הסקריפט sign_target_files_apks מקבל כקלט קובצי יעד .zip ומפיק קובצי יעד חדשים .zip שבהם נחתמו כל הקבצים של .apk באמצעות מפתחות חדשים. התמונות החתוחות החדשות נמצאות בקטע IMAGES/ ב-signed-target_files.zip.

חתימה על חבילות OTA

אפשר להמיר קובץ ZIP של קובצי יעד חתומים לקובץ ZIP של עדכון OTA חתום באמצעות התהליך הבא:
ota_from_target_files \
-k  (--package_key) 
signed-target_files.zip \
signed-ota_update.zip

חתימות וטעינה צדדית

התקנה צידית לא עוקפת את מנגנון האימות הרגיל של חתימת חבילה בתהליך השחזור – לפני שמתקינים חבילה, השחזור יוודא שהחתימה חתומה באמצעות אחד מהמפתחות הפרטיים שתואמים למפתחות הציבוריים שמאוחסנים במחיצת השחזור, בדיוק כמו בחבילה שנמסרת בחיבור אלחוטי.

חבילות עדכון שמתקבלות מהמערכת הראשית מאומתות בדרך כלל פעמיים: פעם אחת על ידי המערכת הראשית, באמצעות שיטת RecoverySystem.verifyPackage() ב-API של Android, ואז שוב באמצעות שחזור. ‏RecoverySystem API בודק את החתימה מול מפתחות ציבוריים שמאוחסנים במערכת הראשית, בקובץ /system/etc/security/otacerts.zip (כברירת מחדל). השחזור בודק את החתימה מול מפתחות ציבוריים שמאוחסנים בדיסק ה-RAM של מחיצת השחזור, בקובץ /res/keys.

כברירת מחדל, קובצי היעד .zip שנוצרים על ידי ה-build מגדירים את אישור ה-OTA כך שיתאים למפתח הבדיקה. בתמונה שפורסמה, צריך להשתמש באישור אחר כדי שהמכשירים יוכלו לאמת את האותנטיות של חבילת העדכון. העברת הדגל -o אל sign_target_files_apks, כפי שמתואר בקטע הקודם, מחליפה את אישור מפתח הבדיקה באישור מפתח הגרסה המהדורה מהספרייה certs.

בדרך כלל, קובץ האימג' של המערכת וקובץ האימג' לשחזור שומרים על אותה קבוצה של מפתחות ציבוריים ל-OTA. הוספת מפתח רק לקבוצת המפתחות לשחזור מאפשרת לחתום על חבילות שאפשר להתקין רק באמצעות טעינה צדדית (בהנחה שמנגנון ההורדה של העדכון במערכת הראשית מבצע אימות נכון מול otacerts.zip). כדי לציין מפתחות נוספים שייכללו רק בתהליך השחזור, מגדירים את המשתנה PRODUCT_EXTRA_RECOVERY_KEYS בהגדרת המוצר:

vendor/yoyodyne/tardis/products/tardis.mk
 [...]

PRODUCT_EXTRA_RECOVERY_KEYS := vendor/yoyodyne/security/tardis/sideload

זה כולל את המפתח הציבורי vendor/yoyodyne/security/tardis/sideload.x509.pem בקובץ מפתחות השחזור, כדי שיוכל להתקין חבילות שנחתמו איתו. עם זאת, המפתח הנוסף לא כלול בקובץ otacerts.zip, כך שמערכות שמאמתות בצורה נכונה את החבילות שהורדתם לא מפעילות שחזור לחבילות שחתומות במפתח הזה.

אישורים ומפתחות פרטיים

כל מפתח מגיע בשני קבצים: האישור, עם הסיומת ‎ .x509.pem, והמפתח הפרטי, עם הסיומת ‎ .pk8. המפתח הפרטי צריך להישמר בסודיות והוא נדרש כדי לחתום על חבילה. המפתח עצמו יכול להיות מוגן באמצעות סיסמה. לעומת זאת, האישור מכיל רק את החלק הציבורי של המפתח, כך שניתן להפיץ אותו באופן נרחב. משמש לאימות שהחבילה נחתמה על ידי המפתח הפרטי המתאים.

גרסת ה-build הרגילה של Android משתמשת בחמישה מפתחות, שכולם נמצאים ב- build/target/product/security:

testkey
מפתח ברירת מחדל גנרי לחבילות שלא מצוין בהן מפתח אחר.
פלטפורמה
מפתח בדיקה לחבילות שנכללות בפלטפורמת הליבה.
משותף
בדיקת המפתח לדברים ששותפו בתהליך של מסך הבית או אנשי הקשר.
מדיה
מפתח בדיקה לחבילות שנכללות במערכת המדיה או ההורדות.
networkstack
מפתח בדיקה לחבילות שנכללות במערכת הרשתות. מפתח ה-Networktack משמש לחתימה על קבצים בינאריים שתוכננו כרכיבי מערכת מודולריים . אם עדכוני המודול נוצרים בנפרד ומשולבים כקובצי build מוכנים מראש בתמונת המכשיר, יכול להיות שלא תצטרכו ליצור מפתח של networkstack בעץ המקור של Android.

כדי לציין את אחד מהמפתחות האלה בחבילות ספציפיות, מגדירים את LOCAL_CERTIFICATE בקובץ Android.mk. (אם המשתנה הזה לא מוגדר, המערכת משתמשת ב-testkey). אפשר גם לציין מפתח שונה לגמרי לפי שם נתיב, לדוגמה:

device/yoyodyne/apps/SpecialApp/Android.mk
 [...]

LOCAL_CERTIFICATE := device/yoyodyne/security/special

עכשיו ה-build משתמש במקש device/yoyodyne/security/special.{x509.pem,pk8} כדי לחתום על SpecialApp.APK. ה-build יכול להשתמש רק במפתחות פרטיים שלא מוגנים באמצעות סיסמה.

אפשרויות חתימה מתקדמות

החלפת מפתח לחתימה על קובץ APK

סקריפט החתימה sign_target_files_apks פועל על קובצי היעד שנוצרו ל-build. כל המידע על האישורים והמפתחות הפרטיים שנעשה בהם שימוש בזמן ה-build נכלל בקובצי היעד. כשמריצים את סקריפט החתימה כדי לחתום על הגרסה המשוחררת, אפשר להחליף את מפתחות החתימה על סמך שם המפתח או שם ה-APK.

משתמשים בדגלים --key_mapping ו---default_key_mappings כדי לציין החלפת מפתחות על סמך שמות המפתחות:

  • הדגל --key_mapping src_key=dest_key מציין את ההחלפה של מפתח אחד בכל פעם.
  • הדגל --default_key_mappings dir מציין תיקייה עם חמישה מפתחות שתחליף את כל המפתחות ב-build/target/product/security. זה שווה ערך לשימוש ב---key_mapping חמש פעמים כדי לציין את המיפויים.
build/target/product/security/testkey      = dir/releasekey
build/target/product/security/platform     = dir/platform
build/target/product/security/shared       = dir/shared
build/target/product/security/media        = dir/media
build/target/product/security/networkstack = dir/networkstack

משתמשים בדגל --extra_apks apk_name1,apk_name2,...=key כדי לציין את החלפות מפתחות החתימה על סמך שמות ה-APK. אם השדה key נשאר ריק, הסקריפט מתייחס לחבילות ה-APK שצוינו כחתימות מראש.

במוצר התיאורטי של Tardis, נדרשים שישה מפתחות שמוגנים באמצעות סיסמה: חמישה כדי להחליף את החמישה ב-build/target/product/security, ואחד כדי להחליף את המפתח הנוסף device/yoyodyne/security/special שנדרש ל-SpecialApp בדוגמה שלמעלה. אם המפתחות היו בתיקיות הבאות:

vendor/yoyodyne/security/tardis/releasekey.x509.pem
vendor/yoyodyne/security/tardis/releasekey.pk8
vendor/yoyodyne/security/tardis/platform.x509.pem
vendor/yoyodyne/security/tardis/platform.pk8
vendor/yoyodyne/security/tardis/shared.x509.pem
vendor/yoyodyne/security/tardis/shared.pk8
vendor/yoyodyne/security/tardis/media.x509.pem
vendor/yoyodyne/security/tardis/media.pk8
vendor/yoyodyne/security/tardis/networkstack.x509.pem
vendor/yoyodyne/security/tardis/networkstack.pk8
vendor/yoyodyne/security/special.x509.pem
vendor/yoyodyne/security/special.pk8           # NOT password protected
vendor/yoyodyne/security/special-release.x509.pem
vendor/yoyodyne/security/special-release.pk8   # password protected

לאחר מכן, צריך לחתום על כל האפליקציות באופן הבא:

./build/make/tools/releasetools/sign_target_files_apks \
    --default_key_mappings vendor/yoyodyne/security/tardis \
    --key_mapping vendor/yoyodyne/security/special=vendor/yoyodyne/security/special-release \
    --extra_apks PresignedApp= \
    -o tardis-target_files.zip \
    signed-tardis-target_files.zip

יוצגו האפשרויות הבאות:

Enter password for vendor/yoyodyne/security/special-release key>
Enter password for vendor/yoyodyne/security/tardis/networkstack key>
Enter password for vendor/yoyodyne/security/tardis/media key>
Enter password for vendor/yoyodyne/security/tardis/platform key>
Enter password for vendor/yoyodyne/security/tardis/releasekey key>
Enter password for vendor/yoyodyne/security/tardis/shared key>
    signing: Phone.apk (vendor/yoyodyne/security/tardis/platform)
    signing: Camera.apk (vendor/yoyodyne/security/tardis/media)
    signing: NetworkStack.apk (vendor/yoyodyne/security/tardis/networkstack)
    signing: Special.apk (vendor/yoyodyne/security/special-release)
    signing: Email.apk (vendor/yoyodyne/security/tardis/releasekey)
        [...]
    signing: ContactsProvider.apk (vendor/yoyodyne/security/tardis/shared)
    signing: Launcher.apk (vendor/yoyodyne/security/tardis/shared)
NOT signing: PresignedApp.apk
        (skipped due to special cert string)
rewriting SYSTEM/build.prop:
  replace:  ro.build.description=tardis-user Eclair ERC91 15449 test-keys
     with:  ro.build.description=tardis-user Eclair ERC91 15449 release-keys
  replace: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/test-keys
     with: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/release-keys
    signing: framework-res.apk (vendor/yoyodyne/security/tardis/platform)
rewriting RECOVERY/RAMDISK/default.prop:
  replace:  ro.build.description=tardis-user Eclair ERC91 15449 test-keys
     with:  ro.build.description=tardis-user Eclair ERC91 15449 release-keys
  replace: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/test-keys
     with: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/release-keys
using:
    vendor/yoyodyne/security/tardis/releasekey.x509.pem
for OTA package verification
done.

אחרי שהמשתמש מתבקש להזין סיסמאות לכל המפתחות שמוגנים באמצעות סיסמה, הסקריפט חותם מחדש על כל קובצי ה-APK ביעד הקלט .zip באמצעות מפתחות הגרסה. לפני שמריצים את הפקודה, אפשר גם להגדיר את משתנה הסביבה ANDROID_PW_FILE לשם של קובץ זמני. לאחר מכן, הסקריפט יפעיל את העורך כדי לאפשר לכם להזין סיסמאות לכל המפתחות (זו יכולה להיות דרך נוחה יותר להזין סיסמאות).

החלפת מפתח החתימה של APEX

ב-Android 10 נוסף פורמט הקובץ APEX להתקנת מודולים ברמה נמוכה יותר של המערכת. כפי שמוסבר בקטע חתימה על קובצי APEX, כל קובץ APEX נחתם בשני מפתחות: אחד לתמונת מערכת הקבצים המינימלית בתוך קובץ APEX והשני לכל קובץ APEX.

כשחותמים על גרסה, שני מפתחות החתימה של קובץ APEX מוחלפים במפתחות גרסה. מפתח המטען הייעודי למערכת הקבצים מצוין באמצעות הדגל --extra_apex_payload, ומפתח החתימה המלא של קובץ APEX מצוין באמצעות הדגל --extra_apks.

במוצר tardis, נניח שיש לכם את הגדרת המפתחות הבאה לקובצי APEX‏ com.android.conscrypt.apex,‏ com.android.media.apex ו-com.android.runtime.release.apex.

name="com.android.conscrypt.apex" public_key="PRESIGNED" private_key="PRESIGNED" container_certificate="PRESIGNED" container_private_key="PRESIGNED"
name="com.android.media.apex" public_key="PRESIGNED" private_key="PRESIGNED" container_certificate="PRESIGNED" container_private_key="PRESIGNED"
name="com.android.runtime.release.apex" public_key="vendor/yoyodyne/security/testkeys/com.android.runtime.avbpubkey" private_key="vendor/yoyodyne/security/testkeys/com.android.runtime.pem" container_certificate="vendor/yoyodyne/security/testkeys/com.google.android.runtime.release_container.x509.pem" container_private_key="vendor/yoyodyne/security/testkeys/com.google.android.runtime.release_container.pk8"

ויש לכם את הקבצים הבאים שמכילים את מפתחות הגרסה:

vendor/yoyodyne/security/runtime_apex_container.x509.pem
vendor/yoyodyne/security/runtime_apex_container.pk8
vendor/yoyodyne/security/runtime_apex_payload.pem

הפקודה הבאה מבטלת את מפתחות החתימה של com.android.runtime.release.apex ו-com.android.tzdata.apex במהלך חתימת הגרסה. באופן ספציפי, הקובץ com.android.runtime.release.apex חתום באמצעות מפתחות השחרור שצוינו (runtime_apex_container לקובץ APEX ו-runtime_apex_payload לתוכן של קובץ התמונה). המערכת מתייחסת ל-com.android.tzdata.apex כחתום מראש. כל שאר קובצי APEX מטופלים לפי הגדרת ברירת המחדל שמפורטת בקובצי היעד.

./build/make/tools/releasetools/sign_target_files_apks \
    --default_key_mappings   vendor/yoyodyne/security/tardis \
    --extra_apks             com.android.runtime.release.apex=vendor/yoyodyne/security/runtime_apex_container \
    --extra_apex_payload_key com.android.runtime.release.apex=vendor/yoyodyne/security/runtime_apex_payload.pem \
    --extra_apks             com.android.media.apex= \
    --extra_apex_payload_key com.android.media.apex= \
    -o tardis-target_files.zip \
    signed-tardis-target_files.zip

הפעלת הפקודה שלמעלה מאפשרת לראות את היומנים הבאים:

        [...]
    signing: com.android.runtime.release.apex                  container (vendor/yoyodyne/security/runtime_apex_container)
           : com.android.runtime.release.apex                  payload   (vendor/yoyodyne/security/runtime_apex_payload.pem)
NOT signing: com.android.conscrypt.apex
        (skipped due to special cert string)
NOT signing: com.android.media.apex
        (skipped due to special cert string)
        [...]

אפשרויות אחרות

סקריפט החתימה sign_target_files_apks כותב מחדש את תיאור ה-build ואת טביעת האצבע בקובצי המאפיינים של ה-build, כדי לשקף שה-build הוא build חתום. הדגל --tag_changes קובע אילו שינויים יבוצעו בטביעת האצבע. מריצים את הסקריפט עם -h כדי לראות את המסמכים של כל הדגלים.

יצירת מפתחות באופן ידני

Android משתמש במפתחות RSA של 2048 ביט עם מעריך ציבורי 3. אפשר ליצור זוגות של אישורים/מפתחות פרטיים באמצעות הכלי openssl מ-openssl.org:

# generate RSA key
openssl genrsa -3 -out temp.pem 2048
Generating RSA private key, 2048 bit long modulus
....+++
.....................+++
e is 3 (0x3)

# create a certificate with the public part of the key
openssl req -new -x509 -key temp.pem -out releasekey.x509.pem -days 10000 -subj '/C=US/ST=California/L=San Narciso/O=Yoyodyne, Inc./OU=Yoyodyne Mobility/CN=Yoyodyne/emailAddress=yoyodyne@example.com'

# create a PKCS#8-formatted version of the private key
openssl pkcs8 -in temp.pem -topk8 -outform DER -out releasekey.pk8 -nocrypt

# securely delete the temp.pem file
shred --remove temp.pem

הפקודה openssl pkcs8 שצוינה למעלה יוצרת קובץ ‎ .pk8 ללא סיסמה, שמתאים לשימוש עם מערכת ה-build. כדי ליצור קובץ ‎.pk8 מאובטח באמצעות סיסמה (כדאי לעשות זאת לכל מפתחות הגרסה בפועל), מחליפים את הארגומנט -nocrypt ב--passout stdin. לאחר מכן, openssl ייצפין את המפתח הפרטי באמצעות סיסמה שנקראה מהקלט הרגיל. לא מודפסת הנחיה, כך שאם stdin הוא המסוף, נראה שהתוכנית תקועה בזמן שהיא בעצם רק ממתינה להזנת סיסמה. אפשר להשתמש בערכים אחרים לארגומנט the-passout כדי לקרוא את הסיסמה ממיקומים אחרים. פרטים נוספים זמינים ב מסמכי התיעוד של openssl.

קובץ הביניים temp.pem מכיל את המפתח הפרטי ללא הגנה מסוג כלשהו באמצעות סיסמה, לכן חשוב להיפטר ממנו בצורה מושכלת כשיוצרים מפתחות גרסה. באופן ספציפי, יכול להיות שהכלי GNUshred לא יהיה יעיל במערכות קבצים ברשת או במערכות קבצים עם יומן. כדי לוודא שהמפתחות הביניים לא נחשפים בטעות, אפשר להשתמש בספריית עבודה שנמצאת בדיסק RAM (למשל, מחיצה של tmpfs) כשיוצרים מפתחות.

יצירת קובצי תמונה

כשיש לכם את signed-target_files.zip, אתם צריכים ליצור את התמונה כדי שתוכלו להעביר אותה למכשיר. כדי ליצור את קובץ האימג' החתום מקובצי היעד, מריצים את הפקודה הבאה מהשורש של עץ Android:

img_from_target_files signed-target_files.zip signed-img.zip
הקובץ שנוצר, signed-img.zip, מכיל את כל הקבצים מסוג .img. כדי לטעון קובץ אימג' למכשיר, משתמשים ב-fastboot באופן הבא:
fastboot update signed-img.zip