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

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

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

מפתחות גרסה

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

כדי ליצור קבוצה ייחודית משלכם של מפתחות חתימה, מריצים את הפקודות הבאות מהשורש של עץ 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 כך שישקף את פרטי הארגון. אפשר להשתמש בכל ספרייה, אבל חשוב לבחור מיקום מגובה ומאובטח. יש ספקים שבוחרים להצפין את המפתח הפרטי שלהם באמצעות ביטוי סיסמה חזק ולאחסן את המפתח המוצפן בבקרת מקור. אחרים מאחסנים את מפתחות השחרור שלהם במקום אחר לגמרי, למשל במחשב עם פער אוויר.

כדי ליצור תמונה של גרסת הפצה, משתמשים בפקודה:

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 מקבל קובץ target-files .zip כקלט ומפיק קובץ חדש target-files .zip שבו כל הקבצים .apk נחתמו באמצעות מפתחות חדשים. התמונות החדשות עם החתימה נמצאות בתיקייה IMAGES/ בתוך signed-target_files.zip.

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

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

חתימות והעלאה צדדית

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

בדרך כלל, חבילות עדכון שמתקבלות מהמערכת הראשית מאומתות פעמיים: פעם אחת על ידי המערכת הראשית, באמצעות ה-method‏ RecoverySystem.verifyPackage() ב-Android API, ופעם נוספת על ידי recovery. ‫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
מפתח בדיקה לחבילות ששייכות למערכת הרשת. המפתח networkstack משמש לחתימה על קבצים בינאריים שנועדו להיות רכיבי מערכת מודולריים . אם עדכוני המודולים שלכם נוצרים בנפרד ומשולבים כרכיבים מוכנים מראש בתמונת המכשיר, יכול להיות שלא תצטרכו ליצור מפתח networkstack בעץ המקור של Android.

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

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

LOCAL_CERTIFICATE := device/yoyodyne/security/special

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

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

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

סקריפט החתימה sign_target_files_apks פועל על קובצי היעד שנוצרו בשביל 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 שצוינו כקובצי 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 לפרסום, שני מפתחות החתימה שלו מוחלפים במפתחות פרסום. מציינים את מפתח המטען הייעודי (payload) של מערכת הקבצים באמצעות הדגל --extra_apex_payload, ואת מפתח החתימה של קובץ ה-APEX כולו באמצעות הדגל --extra_apks.

במוצר tardis, נניח שיש לכם את הגדרת המפתח הבאה לקבצים com.android.conscrypt.apex,‏ com.android.media.apex ו-com.android.runtime.release.apex מסוג 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 של 2,048 ביט עם מעריך ציבורי 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 הוא הטרמינל, התוכנית תיראה כאילו היא נתקעת, כשבפועל היא רק מחכה שתזינו סיסמה. אפשר להשתמש בערכים אחרים לארגומנט passout כדי לקרוא את הסיסמה ממיקומים אחרים. לפרטים, אפשר לעיין ב מסמכי התיעוד של openssl.

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

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

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

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