Add fsverity digest manifest

fsverity digest manifest stores a map from files to fsverity digests.
The manifest is installed as a serialized protobuf file, to a signed apk
system/etc/security/fsverity/BuildManifest.apk.

Bug: 193113311
Test: build with PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA := true
Change-Id: I55fc10400206b8ce0d5f198faea08fe3930b362c
diff --git a/core/Makefile b/core/Makefile
index 2a7dfc6..44d4a9e 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -527,6 +527,16 @@
     $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-image-kernel-modules-dir,GENERIC_RAMDISK,$(TARGET_RAMDISK_OUT),,modules.load,,$(kmd)))))
 
 # -----------------------------------------------------------------
+# FSVerity metadata generation
+ifeq ($(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA),true)
+
+FSVERITY_APK_KEY_PATH := $(DEFAULT_SYSTEM_DEV_CERTIFICATE)
+FSVERITY_APK_OUT := system/etc/security/fsverity/BuildManifest.apk
+FSVERITY_APK_MANIFEST_PATH := system/security/fsverity/AndroidManifest.xml
+
+endif # PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA
+
+# -----------------------------------------------------------------
 # Cert-to-package mapping.  Used by the post-build signing tools.
 # Use a macro to add newline to each echo command
 # $1 stem name of the package
@@ -575,6 +585,8 @@
 	    $(if $(PACKAGES.$(p).EXTERNAL_KEY),\
 	      $(call _apkcerts_write_line,$(PACKAGES.$(p).STEM),EXTERNAL,,$(PACKAGES.$(p).COMPRESSED),$(PACKAGES.$(p).PARTITION),$@),\
 	      $(call _apkcerts_write_line,$(PACKAGES.$(p).STEM),$(PACKAGES.$(p).CERTIFICATE),$(PACKAGES.$(p).PRIVATE_KEY),$(PACKAGES.$(p).COMPRESSED),$(PACKAGES.$(p).PARTITION),$@))))
+	$(if $(filter true,$(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA)),\
+	  $(call _apkcerts_write_line,$(notdir $(basename $(FSVERITY_APK_OUT))),$(FSVERITY_APK_KEY_PATH).x509.pem,$(FSVERITY_APK_KEY_PATH).pk8,,system,$@))
 	# In case value of PACKAGES is empty.
 	$(hide) touch $@
 
@@ -1674,6 +1686,9 @@
     $(if $(PRODUCT_SYSTEM_HEADROOM),$(hide) echo "system_headroom=$(PRODUCT_SYSTEM_HEADROOM)" >> $(1))
     $(if $(filter true,$(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA)),$(hide) echo "fsverity=$(HOST_OUT_EXECUTABLES)/fsverity" >> $(1))
     $(if $(filter true,$(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA)),$(hide) echo "fsverity_generate_metadata=true" >> $(1))
+    $(if $(filter true,$(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA)),$(hide) echo "fsverity_apk_key=$(FSVERITY_APK_KEY_PATH)" >> $(1))
+    $(if $(filter true,$(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA)),$(hide) echo "fsverity_apk_manifest=$(FSVERITY_APK_MANIFEST_PATH)" >> $(1))
+    $(if $(filter true,$(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA)),$(hide) echo "fsverity_apk_out=$(FSVERITY_APK_OUT)" >> $(1))
     $(call add-common-ro-flags-to-image-props,system,$(1))
 )
 $(if $(filter $(2),system_other),\
@@ -2776,7 +2791,8 @@
 $(BUILT_SYSTEMIMAGE): $(BOARD_AVB_SYSTEM_KEY_PATH)
 endif
 ifeq ($(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA),true)
-$(BUILT_SYSTEMIMAGE): $(HOST_OUT_EXECUTABLES)/fsverity
+$(BUILT_SYSTEMIMAGE): $(HOST_OUT_EXECUTABLES)/fsverity $(HOST_OUT_EXECUTABLES)/aapt2 \
+    $(FSVERITY_APK_MANIFEST_PATH) $(FSVERITY_APK_KEY_PATH).x509.pem $(FSVERITY_APK_KEY_PATH).pk8
 endif
 $(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE)
 	$(call build-systemimage-target,$@)
diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp
index 4e628cf..a979a8e 100644
--- a/tools/releasetools/Android.bp
+++ b/tools/releasetools/Android.bp
@@ -265,8 +265,8 @@
     srcs: [
         "fsverity_metadata_generator.py",
     ],
-    required: [
-        "fsverity",
+    libs: [
+        "fsverity_digests_proto_python",
     ],
 }
 
diff --git a/tools/releasetools/build_image.py b/tools/releasetools/build_image.py
index 8e8bcde..8a5d627 100755
--- a/tools/releasetools/build_image.py
+++ b/tools/releasetools/build_image.py
@@ -35,6 +35,7 @@
 import common
 import verity_utils
 
+from fsverity_digests_pb2 import FSVerityDigests
 from fsverity_metadata_generator import FSVerityMetadataGenerator
 
 logger = logging.getLogger(__name__)
@@ -450,6 +451,68 @@
 
   return mkfs_output
 
+def GenerateFSVerityMetadata(in_dir, fsverity_path, apk_key_path, apk_manifest_path, apk_out_path):
+  """Generates fsverity metadata files.
+
+  By setting PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA := true, fsverity
+  metadata files will be generated. For the input files, see `patterns` below.
+
+  One metadata file per one input file will be generated with the suffix
+  .fsv_meta. e.g. system/framework/foo.jar -> system/framework/foo.jar.fsv_meta
+  Also a mapping file containing fsverity digests will be generated to
+  system/etc/security/fsverity/BuildManifest.apk.
+
+  Args:
+    in_dir: temporary working directory (same as BuildImage)
+    fsverity_path: path to host tool fsverity
+    apk_key_path: path to key (e.g. build/make/target/product/security/platform)
+    apk_manifest_path: path to AndroidManifest.xml for APK
+    apk_out_path: path to the output APK
+
+  Returns:
+    None. The files are generated directly under in_dir.
+  """
+
+  patterns = [
+    "system/framework/*.jar",
+    "system/framework/oat/*/*.oat",
+    "system/framework/oat/*/*.vdex",
+    "system/framework/oat/*/*.art",
+    "system/etc/boot-image.prof",
+    "system/etc/dirty-image-objects",
+  ]
+  files = []
+  for pattern in patterns:
+    files += glob.glob(os.path.join(in_dir, pattern))
+  files = sorted(set(files))
+
+  generator = FSVerityMetadataGenerator(fsverity_path)
+  generator.set_hash_alg("sha256")
+
+  digests = FSVerityDigests()
+  for f in files:
+    generator.generate(f)
+    # f is a full path for now; make it relative so it starts with {mount_point}/
+    digest = digests.digests[os.path.relpath(f, in_dir)]
+    digest.digest = generator.digest(f)
+    digest.hash_alg = "sha256"
+
+  temp_dir = common.MakeTempDir()
+
+  os.mkdir(os.path.join(temp_dir, "assets"))
+  metadata_path = os.path.join(temp_dir, "assets", "build_manifest")
+  with open(metadata_path, "wb") as f:
+    f.write(digests.SerializeToString())
+
+  apk_path = os.path.join(in_dir, apk_out_path)
+
+  common.RunAndCheckOutput(["aapt2", "link",
+      "-A", os.path.join(temp_dir, "assets"),
+      "-o", apk_path,
+      "--manifest", apk_manifest_path])
+  common.RunAndCheckOutput(["apksigner", "sign", "--in", apk_path,
+      "--cert", apk_key_path + ".x509.pem",
+      "--key", apk_key_path + ".pk8"])
 
 def BuildImage(in_dir, prop_dict, out_file, target_out=None):
   """Builds an image for the files under in_dir and writes it to out_file.
@@ -479,22 +542,11 @@
     fs_spans_partition = False
 
   if "fsverity_generate_metadata" in prop_dict:
-    patterns = [
-      "system/framework/*.jar",
-      "system/framework/oat/*/*.oat",
-      "system/framework/oat/*/*.vdex",
-      "system/framework/oat/*/*.art",
-      "system/etc/boot-image.prof",
-      "system/etc/dirty-image-objects",
-    ]
-    files = []
-    for pattern in patterns:
-      files += glob.glob(os.path.join(in_dir, pattern))
-    files = sorted(set(files))
-
-    generator = FSVerityMetadataGenerator(prop_dict["fsverity"])
-    for f in files:
-      generator.generate(f)
+    GenerateFSVerityMetadata(in_dir,
+        fsverity_path=prop_dict["fsverity"],
+        apk_key_path=prop_dict["fsverity_apk_key"],
+        apk_manifest_path=prop_dict["fsverity_apk_manifest"],
+        apk_out_path=prop_dict["fsverity_apk_out"])
 
   # Get a builder for creating an image that's to be verified by Verified Boot,
   # or None if not applicable.
@@ -747,6 +799,9 @@
     copy_prop("root_fs_config", "root_fs_config")
     copy_prop("fsverity", "fsverity")
     copy_prop("fsverity_generate_metadata", "fsverity_generate_metadata")
+    copy_prop("fsverity_apk_key","fsverity_apk_key")
+    copy_prop("fsverity_apk_manifest","fsverity_apk_manifest")
+    copy_prop("fsverity_apk_out","fsverity_apk_out")
   elif mount_point == "data":
     # Copy the generic fs type first, override with specific one if available.
     copy_prop("flash_logical_block_size", "flash_logical_block_size")
diff --git a/tools/releasetools/fsverity_metadata_generator.py b/tools/releasetools/fsverity_metadata_generator.py
index 439e484..666efd5 100644
--- a/tools/releasetools/fsverity_metadata_generator.py
+++ b/tools/releasetools/fsverity_metadata_generator.py
@@ -94,6 +94,13 @@
       f.seek(offset + header_len)
       return f.read(size)
 
+  def digest(self, input_file):
+    cmd = [self._fsverity_path, 'digest', input_file]
+    cmd.extend(['--compact'])
+    cmd.extend(['--hash-alg', self._hash_alg])
+    out = subprocess.check_output(cmd, universal_newlines=True).strip()
+    return bytes(bytearray.fromhex(out))
+
   def generate(self, input_file, output_file=None):
     if self._signature != 'none':
       if not self._key: