diff options
Diffstat (limited to 'scripts/image_signing/sign_android_image.sh')
-rwxr-xr-x | scripts/image_signing/sign_android_image.sh | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/scripts/image_signing/sign_android_image.sh b/scripts/image_signing/sign_android_image.sh new file mode 100755 index 00000000..6f999793 --- /dev/null +++ b/scripts/image_signing/sign_android_image.sh @@ -0,0 +1,219 @@ +#!/bin/bash + +# Copyright 2016 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +. "$(dirname "$0")/common.sh" + +set -e + +keytool_bin="/usr/local/buildtools/java/jdk/bin/keytool" + +# Print usage string +usage() { + cat <<EOF +Usage: $PROG /path/to/cros_root_fs/dir /path/to/keys/dir + +Re-sign framework apks in an Android system image. The image itself does not +need to be signed since it is shipped with Chrome OS image, which is already +signed. + +Android has many "framework apks" that are signed with 4 different framework +keys, depends on the purpose of the apk. During development, apks are signed +with the debug one. This script is to re-sign those apks with corresponding +release key. It also handles some of the consequences of the key changes, such +as sepolicy update. + +EOF + if [[ $# -gt 0 ]]; then + error "$*" + exit 1 + fi + exit 0 +} + +# Return name according to the current signing debug key. The name is used to +# select key files. +choose_key() { + local apk="$1" + + local sha1=$(unzip -p "${apk}" META-INF/CERT.RSA | \ + "${keytool_bin}" -printcert | awk '/^\s*SHA1:/ {print $2}') + + # Fingerprints below are generated by: + # $ keytool -file vendor/google/certs/cheetskeys/$NAME.x509.pem -printcert \ + # | grep SHA1: + case "${sha1}" in + "AA:04:E0:5F:82:9C:7E:D1:B9:F8:FC:99:6C:5A:54:43:83:D9:F5:BC") + echo "platform" + ;; + "D4:C4:2D:E0:B9:1B:15:72:FA:7D:A7:21:E0:A6:09:94:B4:4C:B5:AE") + echo "media" + ;; + "38:B6:2C:E1:75:98:E3:E1:1C:CC:F6:6B:83:BB:97:0E:2D:40:6C:AE") + echo "shared" + ;; + "EC:63:36:20:23:B7:CB:66:18:70:D3:39:3C:A9:AE:7E:EF:A9:32:42") + # The above fingerprint is from devkey. Translate to releasekey. + echo "releasekey" + ;; + *) + # Not a framework apk. Do not re-sign. + echo "" + ;; + esac +} + +# Re-sign framework apks with the corresponding release keys. Only apk with +# known key fingerprint are re-signed. We should not re-sign non-framework +# apks. +sign_framework_apks() { + local system_mnt="$1" + local key_dir="$2" + + info "Start signing framework apks" + + # Counters for sanity check. + local counter_platform=0 + local counter_media=0 + local counter_shared=0 + local counter_releasekey=0 + local counter_total=0 + + local apk + while read -d $'\0' -r apk; do + local keyname=$(choose_key "${apk}") + if [[ -z "${keyname}" ]]; then + continue + fi + + info "Re-signing (${keyname}) ${apk}" + + local temp_dir="$(make_temp_dir)" + local temp_apk="${temp_dir}/temp.apk" + local signed_apk="${temp_dir}/signed.apk" + local aligned_apk="${temp_dir}/aligned.apk" + + # Follow the standard manual signing process. See + # https://developer.android.com/studio/publish/app-signing.html. + cp "${apk}" "${temp_apk}" + # Explicitly remove existing signature. + zip -q "${temp_apk}" -d "META-INF/*" + signapk "${key_dir}/$keyname.x509.pem" "${key_dir}/$keyname.pk8" \ + "${temp_apk}" "${signed_apk}" > /dev/null + zipalign 4 "${signed_apk}" "${aligned_apk}" + + sudo mv -f "${aligned_apk}" "${apk}" + + : $(( counter_${keyname} += 1 )) + : $(( counter_total += 1 )) + done < <(find "${system_mnt}/system" -type f -name '*.apk' -print0) + + # Sanity check. + if [[ ${counter_platform} -lt 2 || ${counter_media} -lt 2 || + ${counter_shared} -lt 2 || ${counter_releasekey} -lt 2 || + ${counter_total} -lt 25 ]]; then + die "Number of re-signed package seems to be wrong" + fi +} + +# Platform key is part of the SELinux policy. Since we are re-signing framework +# apks, we need to replace the key in the policy as well. +update_sepolicy() { + local system_mnt=$1 + local key_dir=$2 + + # Only platform is used at this time. + local public_platform_key="${key_dir}/platform.x509.pem" + + info "Start updating sepolicy" + + local new_cert=$(sed -E '/(BEGIN|END) CERTIFICATE/d' \ + "${public_platform_key}" | tr -d '\n' \ + | base64 --decode | hexdump -v -e '/1 "%02x"') + + if [[ -z "${new_cert}" ]]; then + die "Unable to get the public platform key" + fi + + local output=$(make_temp_file) + local xml="${system_mnt}/system/etc/security/mac_permissions.xml" + local pattern='(<signer signature=")\w+("><seinfo value="platform)' + sed -E "s/${pattern}/\1${new_cert}"'\2/g' "${xml}" > "${output}" + + # Sanity check. + if cmp "${xml}" "${output}"; then + die "Failed to replace SELinux policy cert" + fi + + sudo mv -f "${output}" "${xml}" +} + +# Replace the debug key in OTA cert with release key. +replace_ota_cert() { + local system_mnt=$1 + local release_cert=$2 + local ota_zip="${system_mnt}/system/etc/security/otacerts.zip" + + info "Replacing OTA cert" + + local temp_dir=$(make_temp_dir) + pushd "${temp_dir}" > /dev/null + cp "${release_cert}" . + sudo rm "${ota_zip}" + sudo zip -q -r "${ota_zip}" . + popd > /dev/null +} + +# Restore SELinux context. This has to run after all file changes, before +# creating the new squashfs image. +reapply_file_security_context() { + local system_mnt=$1 + local root_fs_dir=$2 + + info "Reapplying file security context" + + sudo /sbin/setfiles -v -r "${system_mnt}" \ + "${root_fs_dir}/etc/selinux/arc/contexts/files/android_file_contexts" \ + "${system_mnt}" +} + +main() { + local root_fs_dir=$1 + local key_dir=$2 + local android_dir="${root_fs_dir}/opt/google/containers/android" + local system_img="${android_dir}/system.raw.img" + + if [[ $# -ne 2 ]]; then + usage "command takes exactly 2 args" + fi + + if [[ ! -f "${system_img}" ]]; then + die "System image does not exist: ${system_img}" + fi + + local working_dir=$(make_temp_dir) + local system_mnt="${working_dir}/mnt" + + info "Unpacking sqaushfs image to ${system_img}" + sudo unsquashfs -f -d "${system_mnt}" "${system_img}" + + sign_framework_apks "${system_mnt}" "${key_dir}" + update_sepolicy "${system_mnt}" "${key_dir}" + replace_ota_cert "${system_mnt}" "${key_dir}/releasekey.x509.pem" + reapply_file_security_context "${system_mnt}" "${root_fs_dir}" + + info "Repacking sqaushfs image" + + local new_system_img="${working_dir}/system.raw.img" + sudo mksquashfs "${system_mnt}" "${new_system_img}" -comp lzo + + local old_size=$(stat -c '%s' "${system_img}") + local new_size=$(stat -c '%s' "${new_system_img}") + info "Android system image size change: ${old_size} -> ${new_size}" + + sudo mv -f "${new_system_img}" "${system_img}" +} + +main "$@" |