diff --git a/core/Makefile b/core/Makefile index 2a7dfc63c5..44d4a9e578 100644 --- a/core/Makefile +++ b/core/Makefile @@ -526,6 +526,16 @@ $(foreach kmd,$(BOARD_KERNEL_MODULE_DIRS), \ $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-recovery-as-boot-load,$(kmd))),\ $(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 @@ -575,6 +585,8 @@ $(APKCERTS_FILE): $(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 $(filter $(2),system),\ $(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 @@ ifeq ($(BOARD_AVB_ENABLE),true) $(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 4e628cfda6..a979a8ece5 100644 --- a/tools/releasetools/Android.bp +++ b/tools/releasetools/Android.bp @@ -265,8 +265,8 @@ python_library_host { 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 8e8bcdeb34..8a5d627b36 100755 --- a/tools/releasetools/build_image.py +++ b/tools/releasetools/build_image.py @@ -35,6 +35,7 @@ import sys 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 @@ def BuildImageMkfs(in_dir, prop_dict, out_file, target_out, fs_config): 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 @@ def BuildImage(in_dir, prop_dict, out_file, target_out=None): 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 @@ def ImagePropFromGlobalDict(glob_dict, mount_point): 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 439e484987..666efd5d61 100644 --- a/tools/releasetools/fsverity_metadata_generator.py +++ b/tools/releasetools/fsverity_metadata_generator.py @@ -94,6 +94,13 @@ class FSVerityMetadataGenerator: 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: