Support AVB signing for BOARD_PREBUILT_BOOTIMAGE

Devices using GKI architecture will use a prebuilt boot.img.
However, we should still sign this prebuilt boot.img with
device-specific AVB keys.

Steps to test the CL.
1. In a device BoardConfig.mk:

   # Uses a prebuilt boot.img
   TARGET_NO_KERNEL := true
   BOARD_PREBUILT_BOOTIMAGE := device/google/redbull/boot.img

   # Enable chained vbmeta for the boot image.
   # The following can be absent, where the hash descriptor of the
   # 'boot' partition will be stored then signed in vbmeta.img instead.
   BOARD_AVB_BOOT_KEY_PATH := external/avb/test/data/testkey_rsa4096.pem
   BOARD_AVB_BOOT_ALGORITHM := SHA256_RSA4096
   BOARD_AVB_BOOT_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP)
   BOARD_AVB_BOOT_ROLLBACK_INDEX_LOCATION := 2

2. `make bootimage`, then `avbtool info_image --image $OUT/boot.img`,
    checks the image is re-signed with a device-specific key

3. `make dist` to generate out/dist/TF.zip

4. `unzip out/dist/TF.zip IMAGES/boot.img`

5. `avbtool info_image --image out/dist/IMAGES/boot.img`,
    checks the image is re-signed with a device-specific key

6. `sign_target_files_apks \
      --avb_boot_key=external/avb/test/data/testkey_rsa8192.pem \
      --avb_boot_algorithm=SHA256_RSA8192 \
      --avb_boot_extra_args="--prop test:sign" \
      ./out/dist/*-target_files-eng.*.zip signed.zip`, resign the TF.zip

7. `unzip signed.zip IMAGES/boot.img`, then use `avbtool info_image` to
   check the boot.img is re-signed with the --avb_boot_key in step 6.

Bug: 188485657
Test: above steps
Change-Id: I7ee8b3ffe6a86aaca34bbb7a8898a97b3f8bd801
diff --git a/core/Makefile b/core/Makefile
index fbce430..c64532f 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -1036,7 +1036,20 @@
 ifdef BOARD_PREBUILT_BOOTIMAGE
 INTERNAL_PREBUILT_BOOTIMAGE := $(BOARD_PREBUILT_BOOTIMAGE)
 INSTALLED_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img
-$(eval $(call copy-one-file,$(INTERNAL_PREBUILT_BOOTIMAGE),$(INSTALLED_BOOTIMAGE_TARGET)))
+
+ifeq ($(BOARD_AVB_ENABLE),true)
+$(INSTALLED_BOOTIMAGE_TARGET): $(INTERNAL_PREBUILT_BOOTIMAGE) $(AVBTOOL) $(BOARD_AVB_BOOT_KEY_PATH)
+	cp $(INTERNAL_PREBUILT_BOOTIMAGE) $@
+	$(AVBTOOL) add_hash_footer \
+	    --image $@ \
+	    --partition_size $(BOARD_BOOTIMAGE_PARTITION_SIZE) \
+	    --partition_name boot $(INTERNAL_AVB_BOOT_SIGNING_ARGS) \
+	    $(BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS)
+else
+$(INSTALLED_BOOTIMAGE_TARGET): $(INTERNAL_PREBUILT_BOOTIMAGE)
+	cp $(INTERNAL_PREBUILT_BOOTIMAGE) $@
+endif # BOARD_AVB_ENABLE
+
 else # BOARD_PREBUILT_BOOTIMAGE not defined
 INSTALLED_BOOTIMAGE_TARGET :=
 endif # BOARD_PREBUILT_BOOTIMAGE
@@ -5107,12 +5120,17 @@
 	$(hide) mkdir -p $(zip_root)/IMAGES
 	$(hide) cp $(INSTALLED_SYSTEM_EXTIMAGE_TARGET) $(zip_root)/IMAGES/
 endif
+ifndef BOARD_PREBUILT_BOOTIMAGE
 ifneq (,$(INTERNAL_PREBUILT_BOOTIMAGE) $(filter true,$(BOARD_COPY_BOOT_IMAGE_TO_TARGET_FILES)))
 ifdef INSTALLED_BOOTIMAGE_TARGET
 	$(hide) mkdir -p $(zip_root)/IMAGES
 	$(hide) cp $(INSTALLED_BOOTIMAGE_TARGET) $(zip_root)/IMAGES/
 endif # INSTALLED_BOOTIMAGE_TARGET
 endif # INTERNAL_PREBUILT_BOOTIMAGE != "" || BOARD_COPY_BOOT_IMAGE_TO_TARGET_FILES == true
+else # BOARD_PREBUILT_BOOTIMAGE is defined
+	$(hide) mkdir -p $(zip_root)/PREBUILT_IMAGES
+	$(hide) cp $(INSTALLED_BOOTIMAGE_TARGET) $(zip_root)/PREBUILT_IMAGES/
+endif # BOARD_PREBUILT_BOOTIMAGE
 ifdef BOARD_PREBUILT_ODMIMAGE
 	$(hide) mkdir -p $(zip_root)/IMAGES
 	$(hide) cp $(INSTALLED_ODMIMAGE_TARGET) $(zip_root)/IMAGES/
diff --git a/core/board_config.mk b/core/board_config.mk
index 9061342..1b08f9a 100644
--- a/core/board_config.mk
+++ b/core/board_config.mk
@@ -379,6 +379,8 @@
 ifeq ($(PRODUCT_BUILD_BOOT_IMAGE),)
   ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
     BUILDING_BOOT_IMAGE :=
+  else ifdef BOARD_PREBUILT_BOOTIMAGE
+    BUILDING_BOOT_IMAGE :=
   else ifdef BOARD_BOOTIMAGE_PARTITION_SIZE
     BUILDING_BOOT_IMAGE := true
   else ifneq (,$(foreach kernel,$(BOARD_KERNEL_BINARIES),$(BOARD_$(call to-upper,$(kernel))_BOOTIMAGE_PARTITION_SIZE)))
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 5e2a50d..985a21a 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -1710,6 +1710,38 @@
   return data
 
 
+def _SignBootableImage(image_path, prebuilt_name, partition_name,
+                       info_dict=None):
+  """Performs AVB signing for a prebuilt boot.img.
+
+  Args:
+    image_path: The full path of the image, e.g., /path/to/boot.img.
+    prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
+        boot-5.10.img, recovery.img.
+    partition_name: The partition name, e.g., 'boot' or 'recovery'.
+    info_dict: The information dict read from misc_info.txt.
+  """
+  if info_dict is None:
+    info_dict = OPTIONS.info_dict
+
+  # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
+  if info_dict.get("avb_enable") == "true":
+    avbtool = info_dict["avb_avbtool"]
+    if partition_name == "recovery":
+      part_size = info_dict["recovery_size"]
+    else:
+      part_size = info_dict[prebuilt_name.replace(".img", "_size")]
+
+    cmd = [avbtool, "add_hash_footer", "--image", image_path,
+           "--partition_size", str(part_size), "--partition_name",
+           partition_name]
+    AppendAVBSigningArgs(cmd, partition_name)
+    args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
+    if args and args.strip():
+      cmd.extend(shlex.split(args))
+    RunAndCheckOutput(cmd)
+
+
 def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
                      info_dict=None, two_step_image=False):
   """Return a File object with the desired bootable image.
@@ -1718,6 +1750,9 @@
   otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
   the source files in 'unpack_dir'/'tree_subdir'."""
 
+  if info_dict is None:
+    info_dict = OPTIONS.info_dict
+
   prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
   if os.path.exists(prebuilt_path):
     logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
@@ -1728,10 +1763,16 @@
     logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
     return File.FromLocalFile(name, prebuilt_path)
 
-  logger.info("building image from target_files %s...", tree_subdir)
+  prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
+  if os.path.exists(prebuilt_path):
+    logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
+    signed_img = MakeTempFile()
+    shutil.copy(prebuilt_path, signed_img)
+    partition_name = tree_subdir.lower()
+    _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
+    return File.FromLocalFile(name, signed_img)
 
-  if info_dict is None:
-    info_dict = OPTIONS.info_dict
+  logger.info("building image from target_files %s...", tree_subdir)
 
   # With system_root_image == "true", we don't pack ramdisk into the boot image.
   # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk