Android下的配置管理之道之對 OTA 更新包進行簽名

對要發佈的版本進行簽名

Android 操作系統映像在兩個地方使用加密簽名:

  • 映像中的所有 .apk 文件都必須經過簽名。Android 軟件包管理器通過下列兩種方式使用 .apk 簽名:

更換應用時,必須使用與舊應用相同的密鑰對其簽名,才能存取舊應用的數據。無論是通過覆蓋 .apk 來更新用戶應用,還是使用安裝在 /data
下的新版本應用來覆蓋系統應用,這一點都適用。 如果兩個或多個應用想要共享同一個用戶
ID(方便共享數據等),則必須使用相同的密鑰對它們進行簽名。

  • 必須使用符合系統預期的密鑰對 OTA 更新包進行簽名,否則在安裝過程中 OTA 更新包將被拒絕。

發佈密鑰

Android 樹的 build/target/product/security 目錄中提供了測試密鑰。使用 make 編譯 Android 操作系統映像便可使用這些測試密鑰對所有 .apk 文件進行簽名。由於這些測試密鑰是公開的,任何人都可以使用相同的密鑰對他們自己的 .apk 文件簽名,這樣他們就能夠替換或盜用您的操作系統映像中編譯的系統應用。因此,您必須使用只有您自己才能訪問的特殊“發佈密鑰”集對公開發布或部署的 Android 操作系統映像進行簽名。

要生成您自己的唯一發布密鑰集,請在 Android 樹的根目錄下運行以下命令:

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

您需要對 $subject 進行更改以反映貴組織的信息。您可以使用任何目錄,但要注意選擇一個已備份且安全的位置。部分供應商會使用強密碼加密私鑰,並將其存儲在源代碼控制系統中;其他供應商則將他們的發佈密鑰完全存儲在其他地方,如氣隙阻隔的計算機上。

要生成發佈映像,請使用以下命令:

make dist
./build/tools/releasetools/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 文件都已使用新密鑰簽名。您可以在 signed-target_files.zip 中的 IMAGES/ 目錄下找到新簽名的映像。

對 OTA 更新包進行簽名

您可以按照以下步驟將已簽名的目標文件 zip 轉換爲已簽名的 OTA 更新 zip:

./build/tools/releasetools/ota_from_target_files \
    --key_mapping ~/.android-certs/releasekey \
    signed-target_files.zip \
    signed-ota_update.zip

簽名和旁加載

旁加載不會繞過 Recovery 流程中的正常軟件包簽名驗證機制。在安裝一個軟件包之前,Recovery 會驗證該軟件包是否由與 Recovery 分區中存儲的公鑰相匹配的私鑰進行簽名,這與利用無線方式傳輸的軟件包的處理方式一樣。

從主系統收到的更新包通常要經過兩次驗證:一次是由主系統使用 Android API 中的 RecoverySystem.verifyPackage() 方法進行驗證,另一次是通過 Recovery 進行驗證。RecoverySystem API 對照存儲在主系統 /system/etc/security/otacerts.zip 文件(默認情況下)中的公鑰對簽名進行檢查。Recovery 對照存儲在 Recovery 分區 RAM 磁盤中的 /res/keys 文件中的公鑰對簽名進行檢查。

默認情況下,由此版本生成的目標文件 .zip 會將 OTA 證書設置爲與測試密鑰相匹配。在發佈的映像上,必須使用不同的證書,這樣設備才能驗證更新包的真實性。如前面一部分所示,將 -o 標誌傳遞到 sign_target_files_apks 即可將測試密鑰證書替換成您的證書目錄中的發佈密鑰證書。

通常情況下,系統映像和 Recovery 映像存儲的是相同的 OTA 公鑰集。通過將密鑰僅添加至 Recovery 密鑰集,可對只能通過旁加載安裝的 apk 包(假設主系統的更新下載機制正確地對照 otacerts.zip 進行驗證)簽名。您可以通過在產品定義中設置 PRODUCT_EXTRA_RECOVERY_KEYS 變量來指定其他僅可納入 Recovery 中的密鑰:

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

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

其中包括 Recovery 密鑰文件中的公鑰 vendor/yoyodyne/security/tardis/sideload.x509.pem,因此它可以安裝用此公鑰簽名的 apk 包。但 otacerts.zip 中不包含額外的密鑰,因此正確驗證下載包的系統不會針對用此密鑰簽名的 apk 包調用 Recovery。

證書和私鑰

每個密鑰都包含兩個文件:一個是擴展名爲 .x509.pem 的證書,另一個是擴展名爲 .pk8 的私鑰。私鑰需要加以保密,並用於對 apk 包進行簽名。密鑰本身也可能受密碼保護。相比之下,證書只包含公開的一半密鑰,因此可以大範圍地分發。證書被用於驗證某個 apk 包是否由相應的私鑰進行簽名。

標準 Android 版本使用四個密鑰,所有這些密鑰都位於 build/target/product/security 中:

  • testkey 適用於未另外指定密鑰的 apk 包的通用默認密鑰。
  • 平臺 適用於核心平臺所包含的 apk 包的測試密鑰。
  • 共享 適用於家庭/聯繫人進程中的共享內容的測試密鑰。
  • 媒體 適用於媒體/下載系統所包含的 apk 包的測試密鑰。

單個 apk 包通過在其 Android.mk 文件中設置 LOCAL_CERTIFICATE 來指定其中一個密鑰。(如果未設置此變量,則使用 testkey。)您還可以通過路徑名指定完全不同的密鑰,例如:

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

LOCAL_CERTIFICATE := device/yoyodyne/security/special

現在,此版本使用 device/yoyodyne/security/special.{x509.pem,pk8} 密鑰來對 SpecialApp.apk 進行簽名。此版本僅可使用不受密碼保護的私鑰。

高級簽名選項

APK 簽名密鑰替換
簽名腳本 sign_target_files_apks 適用於爲某個版本生成的目標文件。編譯時使用的證書和私鑰的所有相關信息都包含在目標文件中。在運行簽名腳本以針對發佈進行簽名時,可以基於密鑰名稱或 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

使用 --extra_apks apk_name1,apk_name2,...=key 標記可基於 APK 名稱指定替換籤名密鑰。如果 key 留空,則腳本會將指定的 APK 視爲已預簽名。

對於假定的 tardis 產品,您需要五個受密碼保護的密鑰:四個用於替換 build/target/product/security 中的四個密鑰,其餘一個用於替換 SpecialApp 所需的另外一個密鑰 device/yoyodyne/security/special(請參見上面的示例)。如果密鑰位於以下文件中:

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/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/tools/releasetools/sign_target_files_apks \
    --default_key_mappings vendor/yoyodyne/security/tardis \
    --key_mapping vendor/yoyodyne/special=vendor/yoyodyne/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/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: 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.

在提示用戶輸入所有受密碼保護的密鑰的密碼後,腳本會使用發佈密鑰對輸入目標 .zip 中的所有 APK 文件重新簽名。在運行該命令之前,您還可以將 ANDROID_PW_FILE 環境變量設置爲臨時文件名;然後腳本會調用您的編輯器,允許您輸入所有密鑰的密碼(採用該方式輸入密碼可能較爲簡便)。

APEX 簽名密鑰替換

Android 10 引入了 APEX 文件格式,用於安裝較低級別的系統模塊。如 APEX 簽名中所述,每個 APEX 文件都會用兩個密鑰簽名:一個用於 APEX 中的迷你文件系統映像,另一個用於整個 APEX。

針對發佈進行簽名時,APEX 文件的兩個簽名密鑰會被替換爲發佈密鑰。文件系統負載密鑰用 --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.apexcom.android.tzdata.apex 的簽名密鑰。特別是,系統會使用指定的發佈密鑰(對於 APEX 文件,爲 runtime_apex_container;對於文件映像負載,則爲 runtime_apex_payload)爲 com.android.runtime.release.apex 簽名。 com.android.tzdata.apex 會被視爲已預簽名。所有其他 APEX 文件都由目標文件中列出的默認配置處理。

./build/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 簽名腳本會在版本屬性文件中重寫版本描述和指紋,表明這是一個已簽名的版本。–tag_changes 標記可以控制對指紋所做的編輯。使用 -h 運行腳本可以查看所有標記上的文檔。

手動生成密鑰

Android 使用公開指數爲 3 的 2048 位 RSA 密鑰。您可以使用 openssl.org 提供的 openssl 工具來生成證書/私鑰對:

## 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/[email protected]'

# 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 文件,該文件未設置密碼。要創建一個帶有密碼保護的 .pk8 文件(您應當爲所有實際的發佈密鑰執行此步驟),請將 -nocrypt 參數替換爲 -passout stdin;這樣 openssl 將使用從標準輸入中讀取的密碼來加密私鑰。該過程中不會輸出任何提示。因此,當系統確實只是在等待您輸入密碼時,如果 stdin 是終端,程序將會處於掛起狀態。可以對 Passout 參數使用其他值,以便從其他位置讀取密碼;有關詳情,請參閱 openssl 文檔。

temp.pem 中間文件包含不受任何類型的密碼保護的私鑰,因此在生成發佈密鑰時應謹慎處理該文件。需要特別指出的是,GNUshred 實用程序可能對網絡或日誌文件系統無效。在生成密鑰時,您可以使用位於 RAM 磁盤(如 tmpfs 分區)中的工作目錄以確保中間文件不會無意間被暴露。

創建映像文件

一旦您簽署了目標文件 .zip,您便需要創建映像,以便將其存放到設備上。要從目標文件中創建已簽名的映像,請在 Android 樹形結構的根目錄下運行以下命令:

./build/tools/releasetools/img_from_target_files signed-target-files.zip signed-img.zip

生成的文件 signed-img.zip 中包含所有 .img 文件。要將映像加載到設備上,請使用 fastboot,如下所示:
fastboot update signed-img.zip

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章