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