xref: /aosp_15_r20/external/vboot_reference/scripts/image_signing/sign_android_image.sh (revision 8617a60d3594060b7ecbd21bc622a7c14f3cf2bc)
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