1#!/bin/bash 2 3# Copyright 2016 The ChromiumOS Authors 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7. "$(dirname "$0")/common.sh" 8. "$(dirname "$0")/lib/sign_android_lib.sh" 9load_shflags || exit 1 10SCRIPT_DIR="${SCRIPT_DIR:-$(dirname "$0")}" 11 12DEFINE_boolean use_apksigner "${FLAGS_FALSE}" \ 13 "Use apksigner instead of signapk for APK signing" 14 15FLAGS_HELP=" 16Usage: $PROG /path/to/cros_root_fs/dir /path/to/keys/dir [--use_apksigner] 17 18Re-sign framework apks in an Android system image. The image itself does not 19need to be signed since it is shipped with Chrome OS image, which is already 20signed. 21 22Android has many ``framework apks'' that are signed with different framework 23keys, depends on the purpose of the apk. During development, apks are signed 24with the debug one. This script is to re-sign those apks with corresponding 25release key. It also handles some of the consequences of the key changes, such 26as sepolicy update. 27It can also sign using cloud kms keys based on env variables present for it. 28" 29 30# Parse command line. 31FLAGS "$@" || exit 1 32eval set -- "${FLAGS_ARGV}" 33 34set -e 35 36# Re-sign framework apks with the corresponding release keys. Only apk with 37# known key fingerprint are re-signed. We should not re-sign non-framework 38# apks. 39sign_framework_apks() { 40 local system_mnt="$1" 41 local key_dir="$2" 42 local working_dir="$3" 43 local flavor_prop="" 44 local keyset="" 45 46 # Generate key config directory. 47 local gen_key_config_dir 48 gen_key_config_dir="$(make_temp_dir)" 49 local gcloud_provider_class="sun.security.pkcs11.SunPKCS11" 50 51 if [[ -n ${KEYCFG_ANDROID_CLOUD_KEY_PREFIX} ]]; then 52 info "Using cloud signing as cloud key prefix is present." 53 54 if [[ "${FLAGS_use_apksigner}" == "${FLAGS_FALSE}" ]]; then 55 die "use_apksigner flag is required when using gcloud to sign." 56 fi 57 58 # Generate config file needed for gcloud to run the KMS signing. 59 if ! "${SCRIPT_DIR}/lib/generate_android_cloud_config.py" \ 60 -o="${gen_key_config_dir}/"; 61 then 62 die "Unable to generate config for cloud signing, exiting." 63 fi 64 fi 65 66 if ! flavor_prop=$(android_get_build_flavor_prop \ 67 "${system_mnt}/system/build.prop"); then 68 die "Failed to extract build flavor property from \ 69'${system_mnt}/system/build.prop'." 70 fi 71 info "Found build flavor property '${flavor_prop}'." 72 if ! keyset=$(android_choose_signing_keyset "${flavor_prop}"); then 73 die "Unknown build flavor property '${flavor_prop}'." 74 fi 75 info "Expecting signing keyset '${keyset}'." 76 77 if [[ "${FLAGS_use_apksigner}" == "${FLAGS_FALSE}" ]]; then 78 info "Using signapk to sign the Framework apk's." 79 else 80 info "Using apksigner to sign the Framework apk's." 81 fi 82 83 if ! image_content_integrity_check "${system_mnt}" "${working_dir}" \ 84 "Prepare apks signing"; then 85 return 1 86 fi 87 88 # Counters for validity check. 89 local counter_platform=0 90 local counter_media=0 91 local counter_shared=0 92 local counter_releasekey=0 93 local counter_networkstack=0 94 local counter_total=0 95 96 local apk 97 while read -d $'\0' -r apk; do 98 local sha1="" 99 local keyname="" 100 101 sha1=$(unzip -p "${apk}" META-INF/CERT.RSA | \ 102 keytool -printcert | awk '/^\s*SHA1:/ {print $2}') 103 104 if ! keyname=$(android_choose_key "${sha1}" "${keyset}"); then 105 die "Failed to choose signing key for APK '${apk}' (SHA1 '${sha1}') in \ 106build flavor '${flavor_prop}'." 107 fi 108 if [[ -z "${keyname}" ]]; then 109 continue 110 fi 111 112 info "Re-signing (${keyname}) ${apk}" 113 114 local temp_dir="$(make_temp_dir)" 115 local temp_apk="${temp_dir}/temp.apk" 116 local signed_apk="${temp_dir}/signed.apk" 117 118 # Follow the standard manual signing process. See 119 # https://developer.android.com/studio/publish/app-signing.html. 120 cp -a "${apk}" "${temp_apk}" 121 # Explicitly remove existing signature. 122 zip -q "${temp_apk}" -d "META-INF/*" 123 124 if [ "${FLAGS_use_apksigner}" = "$FLAGS_FALSE" ]; then 125 # Signapk now creates signature of APK Signature Scheme v2. No further APK 126 # changes should happen afterward. Also note that signapk now takes care 127 # of zipalign. 128 signapk "${key_dir}/$keyname.x509.pem" "${key_dir}/$keyname.pk8" \ 129 "${temp_apk}" "${signed_apk}" > /dev/null 130 else 131 # Key rotation: old key can sign a new key and generate a lineage file. 132 # Provided the lineage file, Android P can honor the new key. Lineage file 133 # can be generated similar to the following command: 134 # 135 # apksigner rotate --out media.lineage --old-signer --key old-media.pk8 136 # --cert old-media.x509.pem --new-signer --key new-media.pk8 --cert 137 # new-media.x509.pem 138 139 local extra_flags 140 local lineage_file="${key_dir}/${keyname}.lineage" 141 local apksigner_min_sdk_version=28 142 local temp_zipaligned_apk="${temp_dir}/temp_zipaligned.apk" 143 144 # If using apksigner zipalign needs to be done before signing. 145 if ! zipalign -p -f 4 "${temp_apk}" "${temp_zipaligned_apk}"; then 146 die "Zipalign failed to align the apk." 147 fi 148 149 if [[ -f ${lineage_file} ]]; then 150 extra_flags="--lineage ${lineage_file}" 151 fi 152 153 if [[ -n ${KEYCFG_ANDROID_CLOUD_KEY_PREFIX} ]]; then 154 KMS_PKCS11_CONFIG="${key_dir}/${keyname}_config.yaml" \ 155 apksigner sign --v3-signing-enabled true \ 156 --min-sdk-version="${apksigner_min_sdk_version}" \ 157 --provider-class "${gcloud_provider_class}" \ 158 --provider-arg "${gen_key_config_dir}/pkcs11_java.cfg" --ks "NONE" \ 159 --ks-type "PKCS11" \ 160 --ks-key-alias "${KEYCFG_ANDROID_CLOUD_KEY_PREFIX}${keyname}" \ 161 --ks-pass pass:\"\" \ 162 --in "${temp_zipaligned_apk}" --out "${signed_apk}" \ 163 ${extra_flags} 164 else 165 # b/349826228: explicitly disabling v1/v2 signing due to lineage error 166 apksigner sign --key "${key_dir}/${keyname}.pk8" \ 167 --cert "${key_dir}/${keyname}.x509.pem" \ 168 --v1-signing-enabled false --v2-signing-enabled false \ 169 --in "${temp_zipaligned_apk}" --out "${signed_apk}" \ 170 ${extra_flags} 171 fi 172 fi 173 if ! image_content_integrity_check "${system_mnt}" "${working_dir}" \ 174 "sign apk ${signed_apk}"; then 175 return 1 176 fi 177 178 # Copy the content instead of mv to avoid owner/mode changes. 179 #TODO(b/331944273): Fail when copy of apk fails. 180 sudo cp "${signed_apk}" "${apk}" && rm -f "${signed_apk}" 181 182 # Set timestamp rounded to second since squash file system has resolution 183 # in seconds. Required in order for the packages cache generator output is 184 # compatible with the packed file system. 185 sudo touch "${apk}" -t "$(date +%m%d%H%M.%S)" 186 187 : $(( counter_${keyname} += 1 )) 188 : $(( counter_total += 1 )) 189 if ! image_content_integrity_check "${system_mnt}" "${working_dir}" \ 190 "update re-signed apk ${apk}"; then 191 return 1 192 fi 193 done < <(sudo find "${system_mnt}/system" -type f -name '*.apk' -print0) 194 195 info "Found ${counter_platform} platform APKs." 196 info "Found ${counter_media} media APKs." 197 info "Found ${counter_shared} shared APKs." 198 info "Found ${counter_releasekey} release APKs." 199 info "Found ${counter_networkstack} networkstack APKs." 200 info "Found ${counter_total} total APKs." 201 # Validity check. 202 if [[ ${counter_platform} -lt 2 || ${counter_media} -lt 2 || 203 ${counter_shared} -lt 2 || ${counter_releasekey} -lt 2 || 204 ${counter_total} -lt 25 ]]; then 205 die "Number of re-signed package seems to be wrong" 206 fi 207 208 return 0 209} 210 211# Extracts certificate from the provided public key. 212get_cert() { 213 # Full path to public key to read and extract certificate. It must exist. 214 local public_key=$1 215 local cert=$(sed -E '/(BEGIN|END) CERTIFICATE/d' \ 216 "${public_key}" | tr -d '\n' \ 217 | base64 --decode | hexdump -v -e '/1 "%02x"') 218 219 if [[ -z "${cert}" ]]; then 220 die "Unable to get the public platform key" 221 fi 222 echo "${cert}" 223} 224 225# Extract certificate from the provided certificate yaml file. 226get_cert_from_yaml() { 227 # Full path to the yaml file. It must exist, and must have exactly 1 cert. 228 local public_key=$1 229 local cert 230 cert=$(awk '/(BEGIN CERTIFICATE)/,/(END CERTIFICATE)/' \ 231 "${public_key}" | sed -E '/(BEGIN|END) CERTIFICATE/d' | \ 232 tr -d ' ' | tr -d '\n' | base64 --decode | hexdump -v -e '/1 "%02x"') 233 if [[ -z "${cert}" ]]; then 234 die "Unable to get the public platform key" 235 fi 236 echo "${cert}" 237} 238 239# Replaces particular certificate in mac_permissions xml file with new one. 240# Note, this does not fail if particular entry is not found. For example 241# network_stack does not exist in P. 242change_cert() { 243 # Type of signer entry to process. Could be platform, media or network_stack. 244 local type=$1 245 # New certificate encoded to string. This replaces old one. 246 local cert=$2 247 # *mac_permissions xml file to modify, plat_mac_permissions.xml for example. 248 local xml=$3 249 local pattern="(<signer signature=\")\w+(\"><seinfo value=\"${type})" 250 sudo sed -i -E "s/${pattern}/\1${cert}"'\2/g' "${xml}" 251} 252 253# Platform key is part of the SELinux policy. Since we are re-signing framework 254# apks, we need to replace the key in the policy as well. 255update_sepolicy() { 256 local system_mnt=$1 257 local key_dir=$2 258 259 info "Start updating sepolicy" 260 261 local new_platform_cert 262 local new_media_cert 263 local new_network_stack_cert 264 265 if [[ -n ${KEYCFG_ANDROID_CLOUD_KEY_PREFIX} ]]; then 266 local platform_cert_yaml="${key_dir}/platform_config.yaml" 267 local media_cert_yaml="${key_dir}/media_config.yaml" 268 local network_cert_yaml="${key_dir}/networkstack_config.yaml" 269 270 new_platform_cert=$(get_cert_from_yaml "${platform_cert_yaml}") 271 new_media_cert=$(get_cert_from_yaml "${media_cert_yaml}") 272 new_network_stack_cert=$(get_cert_from_yaml "${network_cert_yaml}") 273 else 274 local public_platform_key="${key_dir}/platform.x509.pem" 275 local public_media_key="${key_dir}/media.x509.pem" 276 local public_network_stack_key="${key_dir}/networkstack.x509.pem" 277 278 new_platform_cert=$(get_cert "${public_platform_key}") 279 new_media_cert=$(get_cert "${public_media_key}") 280 new_network_stack_cert=$(get_cert "${public_network_stack_key}") 281 fi 282 283 shopt -s nullglob 284 local xml_list=( "${system_mnt}"/system/etc/**/*mac_permissions.xml ) 285 shopt -u nullglob 286 if [[ "${#xml_list[@]}" -ne 1 ]]; then 287 die "Unexpected number of *mac_permissions.xml: ${#xml_list[@]}\n \ 288 ${xml_list[*]}" 289 fi 290 291 local xml="${xml_list[0]}" 292 local orig=$(make_temp_file) 293 cp "${xml}" "${orig}" 294 295 change_cert "platform" "${new_platform_cert}" "${xml}" 296 change_cert "media" "${new_media_cert}" "${xml}" 297 change_cert "network_stack" "${new_network_stack_cert}" "${xml}" 298 299 # Validity check. 300 if cmp "${xml}" "${orig}"; then 301 die "Failed to replace SELinux policy cert" 302 fi 303} 304 305replace_ota_cert_cloud() { 306 local system_mnt=$1 307 local key_dir=$2 308 local keyname=$3 309 310 info "Replacing OTA cert for cloud" 311 312 local temp_cert 313 temp_cert=$(make_temp_file) 314 local cert 315 cert=$(awk '/(BEGIN CERTIFICATE)/,/(END CERTIFICATE)/' \ 316 "${key_dir}/${keyname}_config.yaml" | sed 's/^\s*//') 317 echo "${cert}" > "${temp_cert}" 318 319 local ota_zip="${system_mnt}/system/etc/security/otacerts.zip" 320 321 local temp_dir 322 temp_dir=$(make_temp_dir) 323 pushd "${temp_dir}" > /dev/null 324 cp "${temp_cert}" "${keyname}.x509.pem" 325 local temp_zip 326 temp_zip=$(make_temp_file) 327 zip -q -r "${temp_zip}.zip" . 328 # Copy the content instead of mv to avoid owner/mode changes. 329 sudo cp "${temp_zip}.zip" "${ota_zip}" 330 popd > /dev/null 331} 332 333# Replace the debug key in OTA cert with release key. 334replace_ota_cert() { 335 local system_mnt=$1 336 local release_cert=$2 337 local ota_zip="${system_mnt}/system/etc/security/otacerts.zip" 338 339 info "Replacing OTA cert" 340 341 local temp_dir=$(make_temp_dir) 342 pushd "${temp_dir}" > /dev/null 343 cp "${release_cert}" . 344 local temp_zip=$(make_temp_file) 345 zip -q -r "${temp_zip}.zip" . 346 # Copy the content instead of mv to avoid owner/mode changes. 347 sudo cp "${temp_zip}.zip" "${ota_zip}" 348 popd > /dev/null 349} 350 351# Snapshot file properties in a directory recursively. 352snapshot_file_properties() { 353 local dir=$1 354 sudo find "${dir}" -exec stat -c '%n:%u:%g:%a' {} + | sort 355} 356 357# Snapshot capabilities in a directory recursively. 358snapshot_capabilities() { 359 local dir=$1 360 sudo find "${dir}" -exec getcap {} + | sort 361} 362 363# Apply capabilities to files in |dir| as specified by |capabilities_list|. 364# See b/179170462. 365apply_capabilities() { 366 local dir=$1 367 local capabilities_list=$2 368 local entry 369 370 while read -ra entry; do 371 if [[ ${#entry[@]} -lt 2 ]]; then 372 error "Unexpected output in capabilities_list of '${entry[*]}'" 373 return 1 374 fi 375 # Output of getcap is either |{file} {capabilities}| or 376 # |{file} = {capabilities}|, so take the first and last element of each 377 # line. 378 info "Setting capabilities ${entry[${#entry[@]}-1]} for ${entry[0]}" 379 sudo setcap "${entry[${#entry[@]}-1]}" "${entry[0]}" 380 done < "${capabilities_list}" 381 382 return 0 383} 384 385# Integrity check that capabilities are unchanged. 386capabilities_integrity_check() { 387 local system_mnt=$1 388 local working_dir=$2 389 snapshot_capabilities "${system_mnt}" > "${working_dir}/capabilities.new" 390 local d 391 if ! d=$(diff "${working_dir}"/capabilities.{orig,new}); then 392 error "Unexpected change of capabilities, diff \n${d}" 393 return 1 394 fi 395 396 return 0 397} 398 399# Integrity check that image content is unchanged. 400image_content_integrity_check() { 401 local system_mnt=$1 402 local working_dir=$2 403 local reason=$3 404 snapshot_file_properties "${system_mnt}" > "${working_dir}/properties.new" 405 local d 406 if ! d=$(diff "${working_dir}"/properties.{orig,new}); then 407 error "Unexpected change of file property, diff due to ${reason}\n${d}" 408 return 1 409 fi 410 411 return 0 412} 413 414list_files_in_squashfs_image() { 415 local unsquashfs=$1 416 local system_img=$2 417 "${unsquashfs}" -l "${system_img}" | grep ^squashfs-root 418} 419 420# This function is needed to set the VB meta digest parameter for 421# Verified Boot. The value is calculated by calculating the hash 422# of hashes of the system and vendor images. It will be written 423# to a file in the same directory as the system image and will be 424# read by ARC Keymint. See go/arc-vboot-param-design for more details. 425write_arcvm_vbmeta_digest() { 426 local android_dir=$1 427 local system_img_path=$2 428 local vendor_img_path=$3 429 430 local vbmeta_digest_path="${android_dir}/arcvm_vbmeta_digest.sha256" 431 432 # Calculate hashes of the system and vendor images. 433 local system_img_hash vendor_img_hash combined_hash vbmeta_digest 434 if ! system_img_hash=$(sha256sum -b "${system_img_path}"); then 435 warn "Error calculating system image hash" 436 return 1 437 fi 438 if ! vendor_img_hash=$(sha256sum -b "${vendor_img_path}"); then 439 warn "Error calculating vendor image hash" 440 return 1 441 fi 442 443 # Cut off the end of sha256sum output since it includes the file name. 444 system_img_hash="$(echo -n "${system_img_hash}" | awk '{print $1}')" 445 vendor_img_hash="$(echo -n "${vendor_img_hash}" | awk '{print $1}')" 446 447 # Combine the two hashes and calculate the hash of that value. 448 combined_hash=$(printf "%s%s" "${system_img_hash}" "${vendor_img_hash}") 449 if ! vbmeta_digest=$(echo -n "${combined_hash}" | sha256sum -b); then 450 warn "Error calculating the hash of the combined hash of the images" 451 return 1 452 fi 453 454 vbmeta_digest="$(echo -n "${vbmeta_digest}" | awk '{print $1}')" 455 456 # If there is an existing digest, compare the two values. 457 if [[ -f "${vbmeta_digest_path}" ]]; then 458 local prev_vbmeta_digest 459 prev_vbmeta_digest=$(cat "${vbmeta_digest_path}") 460 if [[ "${vbmeta_digest}" == "${prev_vbmeta_digest}" ]]; then 461 warn "Error: existing and re-calculated digests are the same" 462 return 1 463 fi 464 fi 465 466 info "Writing re-calculated VB meta digest to arcvm_vbmeta_digest.sha256" 467 echo -n "${vbmeta_digest}" > "${vbmeta_digest_path}" 468 return 0 469} 470 471sign_android_internal() { 472 local root_fs_dir=$1 473 local key_dir=$2 474 475 # Detect vm/container type and set environment correspondingly. 476 # Keep this aligned with 477 # src/private-overlays/project-cheets-private/scripts/board_specific_setup.sh 478 local system_image="" 479 local selinux_dir="${root_fs_dir}/etc/selinux" 480 local file_contexts="" 481 local vm_candidate="${root_fs_dir}/opt/google/vms/android/system.raw.img" 482 local container_candidate=( 483 "${root_fs_dir}/opt/google/containers/android/system.raw.img") 484 if [[ -f "${vm_candidate}" ]]; then 485 system_image="${vm_candidate}" 486 file_contexts="${selinux_dir}/arc/contexts/files/android_file_contexts_vm" 487 elif [[ -f "${container_candidate}" ]]; then 488 system_image="${container_candidate}" 489 file_contexts="${selinux_dir}/arc/contexts/files/android_file_contexts" 490 else 491 die "System image does not exist" 492 fi 493 494 local android_system_image="$(echo \ 495 "${root_fs_dir}"/opt/google/*/android/system.raw.img)" 496 local android_dir=$(dirname "${android_system_image}") 497 local system_img="${android_dir}/system.raw.img" 498 499 # Use the versions in $PATH rather than the system ones. 500 # squashfs-tools 501 local unsquashfs mksquashfs 502 unsquashfs=$(which unsquashfs) 503 mksquashfs=$(which mksquashfs) 504 505 # erofs-utils 506 local dump_erofs fsck_erofs mkfs_erofs 507 dump_erofs=$(which dump.erofs) 508 fsck_erofs=$(which fsck.erofs) 509 mkfs_erofs=$(which mkfs.erofs) 510 511 if [[ $# -lt 2 || $# -gt 3 ]]; then 512 flags_help 513 die "command requires 2 input args and can take 1 optional arguments." 514 fi 515 516 if [[ ! -f "${system_img}" ]]; then 517 die "System image does not exist: ${system_img}" 518 fi 519 520 local image_type 521 local output_squashfs output_erofs 522 if output_squashfs=$("${unsquashfs}" -s "${system_img}" 2>&1); then 523 image_type="squashfs" 524 elif output_erofs=$("${dump_erofs}" "${system_img}" 2>&1); then 525 image_type="erofs" 526 else 527 die "Can't detect the image type of ARC system image\n" \ 528 "output_squashfs: ${output_squashfs}\n" \ 529 "output_erofs: ${output_erofs}" 530 fi 531 info "Detected ARC system image type: ${image_type}" 532 533 # Detect image compression flags. 534 local compression_flags=() 535 local compression_flags_path="${android_dir}/image_compression_flags" 536 if [[ -f "${compression_flags_path}" ]]; then 537 # The file contains a line such as: 538 # mkfs.erofs -z lz4hc -C32768 539 read -r -a tokens <"${compression_flags_path}" 540 if [[ ("${image_type}" == "squashfs" && "${tokens[0]}" != "mksquashfs") || 541 ("${image_type}" == "erofs" && "${tokens[0]}" != "mkfs.erofs") ]]; then 542 die "Compression tool '${tokens[0]}' found in image_compression_flags" \ 543 "doesn't match detected image type '${image_type}'" 544 fi 545 compression_flags=("${tokens[@]:1}") 546 info "Detected compression flags: ${compression_flags[*]}" 547 else 548 if [[ "${image_type}" == "erofs" ]]; then 549 compression_flags=(-z lz4hc -C32768) 550 elif [[ "${image_type}" == "squashfs" ]]; then 551 local compression 552 compression="$("${unsquashfs}" -s "${system_img}" \ 553 | awk '$1 == "Compression" {print $2}')" 554 case "${compression}" in 555 "gzip") 556 compression_flags=(-comp gzip) 557 ;; 558 "lz4") 559 compression_flags=(-comp lz4 -Xhc -b 256K) 560 ;; 561 "zstd") 562 compression_flags=(-comp zstd -b 256K) 563 ;; 564 *) 565 die "Unexpected compression type: ${compression}" 566 ;; 567 esac 568 fi 569 info "image_compression_flags does not exist." \ 570 "(This is expected for versions older than R121-15679.)" \ 571 "Using default compression flags: ${compression_flags[*]}" 572 fi 573 574 if ! type -P zipalign &>/dev/null || ! type -P signapk &>/dev/null \ 575 || ! type -P apksigner &>/dev/null; then 576 # TODO(victorhsieh): Make this an error. This is not treating as error 577 # just to make an unrelated test pass by skipping this signing. 578 warn "Skip signing Android apks (some of executables are not found)." 579 exit 0 580 fi 581 582 local working_dir=$(make_temp_dir) 583 local system_mnt="${working_dir}/mnt" 584 local system_capabilities_orig="${working_dir}/capabilities.orig" 585 586 if [[ "${image_type}" == "squashfs" ]]; then 587 # Extract with xattrs so we can read and audit capabilities. 588 # See b/179170462. 589 info "Unpacking squashfs system image with xattrs to ${system_mnt}" 590 sudo "${unsquashfs}" -x -f -no-progress -d "${system_mnt}" "${system_img}" 591 snapshot_capabilities "${system_mnt}" > "${system_capabilities_orig}" 592 sudo rm -rf "${system_mnt}" 593 594 info "Unpacking squashfs system image without xattrs to ${system_mnt}" 595 list_files_in_squashfs_image "${unsquashfs}" "${system_img}" > \ 596 "${working_dir}/image_file_list.orig" 597 sudo "${unsquashfs}" -no-xattrs -f -no-progress -d \ 598 "${system_mnt}" "${system_img}" 599 600 elif [[ "${image_type}" == "erofs" ]]; then 601 info "Unpacking erofs system image to ${system_mnt}" 602 sudo "${fsck_erofs}" "--extract=${system_mnt}" "${system_img}" 603 604 # Use /system/etc/capabilities_list, as fsck.erofs does not yet support 605 # extraction with xattrs. 606 local capabilities_list="${system_mnt}/system/etc/capabilities_list" 607 if [[ ! -f "${capabilities_list}" ]]; then 608 die "${capabilities_list} does not exist" 609 fi 610 611 # Add ${system_mnt} as a prefix. 612 # Example of a line: "/system/bin/run-as cap_setgid,cap_setuid=ep" 613 sudo sed "s|^|${system_mnt}|" "${capabilities_list}" > \ 614 "${system_capabilities_orig}" 615 616 # List all files inside the image. 617 sudo find "${system_mnt}" > "${working_dir}/image_file_list.orig" 618 fi 619 620 # Override apksigner flag if file is available, ref b/307968835. 621 local use_apksigner_file_path 622 use_apksigner_file_path="${system_mnt}/system/etc/signing_use_apksigner" 623 if [[ -f "${use_apksigner_file_path}" ]]; then 624 info "Changing apksigner flag from ${FLAGS_use_apksigner} to True" 625 FLAGS_use_apksigner=${FLAGS_TRUE} 626 fi 627 628 snapshot_file_properties "${system_mnt}" > "${working_dir}/properties.orig" 629 630 if ! sign_framework_apks "${system_mnt}" "${key_dir}" "${working_dir}"; then 631 return 1 632 fi 633 634 if ! image_content_integrity_check "${system_mnt}" "${working_dir}" \ 635 "sign_framework_apks"; then 636 return 1 637 fi 638 639 update_sepolicy "${system_mnt}" "${key_dir}" 640 if ! image_content_integrity_check "${system_mnt}" "${working_dir}" \ 641 "update_sepolicy"; then 642 return 1 643 fi 644 645 local ota_cert_kn="releasekey" 646 647 if [[ -n ${KEYCFG_ANDROID_CLOUD_KEY_PREFIX} ]]; then 648 replace_ota_cert_cloud "${system_mnt}" "${key_dir}" "${ota_cert_kn}" 649 else 650 replace_ota_cert "${system_mnt}" "${key_dir}/${ota_cert_kn}.x509.pem" 651 fi 652 653 if ! image_content_integrity_check "${system_mnt}" "${working_dir}" \ 654 "replace_ota_cert"; then 655 return 1 656 fi 657 658 # Packages cache needs to be regenerated when the key and timestamp are 659 # changed for apks. 660 local packages_cache="${system_mnt}/system/etc/packages_cache.xml" 661 local file_hash_cache="${system_mnt}/system/etc/file_hash_cache" 662 if [[ -f "${packages_cache}" ]]; then 663 if type -P aapt &>/dev/null; then 664 info "Regenerating packages cache ${packages_cache}" 665 # For the validity check. 666 local packages_before=$(grep "<package " "${packages_cache}" | wc -l) 667 local vendor_mnt=$(make_temp_dir) 668 local vendor_img="${android_dir}/vendor.raw.img" 669 670 if [[ "${image_type}" == "squashfs" ]]; then 671 info "Unpacking squashfs vendor image to ${vendor_mnt}/vendor" 672 # Vendor image is not updated during this step. However we have to 673 # include vendor apks to re-generated packages cache which exists in 674 # one file for both system and vendor images. 675 sudo "${unsquashfs}" -x -f -no-progress -d "${vendor_mnt}/vendor" \ 676 "${vendor_img}" 677 elif [[ "${image_type}" == "erofs" ]]; then 678 info "Unpacking erofs vendor image to ${vendor_mnt}/vendor" 679 sudo "${fsck_erofs}" "--extract=${vendor_mnt}/vendor" "${vendor_img}" 680 fi 681 682 if ! sudo arc_generate_packages_cache "${system_mnt}" "${vendor_mnt}" \ 683 "${working_dir}/packages_cache.xml" \ 684 "${working_dir}/file_hash_cache"; then 685 die "Failed to generate packages cache." 686 fi 687 sudo cp "${working_dir}/packages_cache.xml" "${packages_cache}" 688 sudo cp "${working_dir}/file_hash_cache" "${file_hash_cache}" 689 # Set android-root as an owner. 690 sudo chown 655360:655360 "${packages_cache}" 691 local packages_after=$(grep "<package " "${packages_cache}" | wc -l) 692 if [[ "${packages_before}" != "${packages_after}" ]]; then 693 die "failed to verify the packages count after the regeneration of " \ 694 "the packages cache. Expected ${packages_before} but found " \ 695 "${packages_after} packages in pacakges_cache.xml" 696 fi 697 else 698 warn "aapt tool could not be found. Could not regenerate the packages " \ 699 "cache. Outdated pacakges_cache.xml is removed." 700 sudo rm "${packages_cache}" 701 fi 702 else 703 info "Packages cache ${packages_cache} does not exist. Skip regeneration." 704 fi 705 706 # Apply original capabilities to system image and verify correctness. 707 if ! apply_capabilities "${system_mnt}" "${system_capabilities_orig}"; then 708 return 1 709 fi 710 if ! capabilities_integrity_check "${system_mnt}" "${working_dir}"; then 711 return 1 712 fi 713 714 local old_size=$(stat -c '%s' "${system_img}") 715 # Remove old system image to prevent mksquashfs tries to merge both images. 716 sudo rm -rf "${system_img}" 717 718 if [[ "${image_type}" == "squashfs" ]]; then 719 info "Repacking squashfs image with ${compression_flags[*]}" 720 if ! sudo "${mksquashfs}" "${system_mnt}" "${system_img}" \ 721 -context-file "${file_contexts}" -mount-point "/" \ 722 -no-progress "${compression_flags[@]}"; then 723 die "mksquashfs failed" 724 fi 725 726 list_files_in_squashfs_image "${unsquashfs}" "${system_img}" > \ 727 "${working_dir}/image_file_list.new" 728 729 elif [[ "${image_type}" == "erofs" ]]; then 730 # List all files inside the image. 731 sudo find "${system_mnt}" > "${working_dir}/image_file_list.new" 732 733 info "Repacking erofs image with ${compression_flags[*]}" 734 if ! sudo "${mkfs_erofs}" "${compression_flags[@]}" \ 735 --file-contexts "${file_contexts}" \ 736 "${system_img}" "${system_mnt}"; then 737 die "mkfs.erofs failed" 738 fi 739 fi 740 741 local new_size 742 new_size=$(stat -c '%s' "${system_img}") 743 info "Android system image size change: ${old_size} -> ${new_size}" 744 745 # Calculate the hash of the system and vendor images and store the value 746 # in a file. The digest was initially calculated and written when the 747 # image was built. This recalculates the digest of the signed image and 748 # replaces the original value. 749 # Any changes to the images must occur before this method. 750 if ! write_arcvm_vbmeta_digest "${android_dir}" "${system_img}" "${vendor_img}"; then 751 warn "ARCVM vbmeta digest was not overwritten" 752 fi 753 754 if d=$(grep -v -F -x -f "${working_dir}"/image_file_list.{new,orig}); then 755 # If we have a line in image_file_list.orig which does not appear in 756 # image_file_list.new, it means some files are removed during signing 757 # process. Here we have already deleted the original Android image so 758 # cannot retry. 759 die "Unexpected change of file list\n${d}" 760 fi 761 762 return 0 763} 764 765main() { 766 # TODO(b/175081695): Remove retries once root problem is fixed. 767 local attempts 768 for (( attempts = 1; attempts <= 3; ++attempts )); do 769 if sign_android_internal "$@"; then 770 exit 0 771 fi 772 warn "Could not sign android image due to recoverable error, will retry," \ 773 "attempt # ${attempts}." 774 warn "@@@ALERT@@@" 775 lsof -n 776 dmesg 777 mount 778 done 779 die "Unable to sign Android image; giving up." 780} 781 782main "$@" 783