Loading core/Makefile +21 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 $@ Loading Loading @@ -1672,6 +1684,11 @@ define generate-image-prop-dictionary $(if $(filter $(2),system),\ $(if $(INTERNAL_SYSTEM_OTHER_PARTITION_SIZE),$(hide) echo "system_other_size=$(INTERNAL_SYSTEM_OTHER_PARTITION_SIZE)" >> $(1)) $(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),\ Loading Loading @@ -2773,6 +2790,10 @@ endef 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 $(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,$@) Loading core/product.mk +10 −0 Original line number Diff line number Diff line Loading @@ -440,6 +440,16 @@ _product_single_value_vars += PRODUCT_INSTALL_EXTRA_FLATTENED_APEXES # This option is only meant to be set by GSI products. _product_single_value_vars += PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT # If set, metadata files for the following artifacts will be generated. # - system/framework/*.jar # - system/framework/oat/<arch>/*.{oat,vdex,art} # - system/etc/boot-image.prof # - system/etc/dirty-image-objects # One fsverity metadata container file per one input file will be generated in # system.img, with a suffix ".fsv_meta". e.g. a container file for # "/system/framework/foo.jar" will be "system/framework/foo.jar.fsv_meta". _product_single_value_vars += PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA .KATI_READONLY := _product_single_value_vars _product_list_vars _product_var_list :=$= $(_product_single_value_vars) $(_product_list_vars) Loading tools/releasetools/Android.bp +11 −0 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ python_defaults { ], libs: [ "releasetools_common", "releasetools_fsverity_metadata_generator", "releasetools_verity_utils", ], required: [ Loading Loading @@ -259,6 +260,16 @@ python_library_host { ], } python_library_host { name: "releasetools_fsverity_metadata_generator", srcs: [ "fsverity_metadata_generator.py", ], libs: [ "fsverity_digests_proto_python", ], } python_library_host { name: "releasetools_verity_utils", srcs: [ Loading tools/releasetools/build_image.py +78 −1 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ Usage: build_image input_directory properties_file output_image \\ from __future__ import print_function import glob import logging import os import os.path Loading @@ -34,6 +35,9 @@ import sys import common import verity_utils from fsverity_digests_pb2 import FSVerityDigests from fsverity_metadata_generator import FSVerityMetadataGenerator logger = logging.getLogger(__name__) OPTIONS = common.OPTIONS Loading Loading @@ -447,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. Loading Loading @@ -475,6 +541,13 @@ def BuildImage(in_dir, prop_dict, out_file, target_out=None): elif fs_type.startswith("f2fs") and prop_dict.get("f2fs_compress") == "true": fs_spans_partition = False if "fsverity_generate_metadata" in prop_dict: 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. verity_image_builder = verity_utils.CreateVerityImageBuilder(prop_dict) Loading Loading @@ -589,7 +662,6 @@ def BuildImage(in_dir, prop_dict, out_file, target_out=None): if verity_image_builder: verity_image_builder.Build(out_file) def ImagePropFromGlobalDict(glob_dict, mount_point): """Build an image property dictionary from the global dictionary. Loading Loading @@ -725,6 +797,11 @@ def ImagePropFromGlobalDict(glob_dict, mount_point): copy_prop("system_root_image", "system_root_image") copy_prop("root_dir", "root_dir") 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") Loading tools/releasetools/fsverity_metadata_generator.py 0 → 100644 +231 −0 Original line number Diff line number Diff line #!/usr/bin/env python # # Copyright 2021 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ `fsverity_metadata_generator` generates fsverity metadata and signature to a container file This actually is a simple wrapper around the `fsverity` program. A file is signed by the program which produces the PKCS#7 signature file, merkle tree file , and the fsverity_descriptor file. Then the files are packed into a single output file so that the information about the signing stays together. Currently, the output of this script is used by `fd_server` which is the host- side backend of an authfs filesystem. `fd_server` uses this file in case when the underlying filesystem (ext4, etc.) on the device doesn't support the fsverity feature natively in which case the information is read directly from the filesystem using ioctl. """ import argparse import os import re import shutil import subprocess import sys import tempfile from struct import * class TempDirectory(object): def __enter__(self): self.name = tempfile.mkdtemp() return self.name def __exit__(self, *unused): shutil.rmtree(self.name) class FSVerityMetadataGenerator: def __init__(self, fsverity_path): self._fsverity_path = fsverity_path # Default values for some properties self.set_hash_alg("sha256") self.set_signature('none') def set_key(self, key): self._key = key def set_cert(self, cert): self._cert = cert def set_hash_alg(self, hash_alg): self._hash_alg = hash_alg def set_signature(self, signature): self._signature = signature def _raw_signature(pkcs7_sig_file): """ Extracts raw signature from DER formatted PKCS#7 detached signature file Do that by parsing the ASN.1 tree to get the location of the signature in the file and then read the portion. """ # Note: there seems to be no public python API (even in 3p modules) that # provides direct access to the raw signature at this moment. So, `openssl # asn1parse` commandline tool is used instead. cmd = ['openssl', 'asn1parse'] cmd.extend(['-inform', 'DER']) cmd.extend(['-in', pkcs7_sig_file]) out = subprocess.check_output(cmd, universal_newlines=True) # The signature is the last element in the tree last_line = out.splitlines()[-1] m = re.search('(\d+):.*hl=\s*(\d+)\s*l=\s*(\d+)\s*.*OCTET STRING', last_line) if not m: raise RuntimeError("Failed to parse asn1parse output: " + out) offset = int(m.group(1)) header_len = int(m.group(2)) size = int(m.group(3)) with open(pkcs7_sig_file, 'rb') as f: 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: raise RuntimeError("key must be specified.") if not self._cert: raise RuntimeError("cert must be specified.") if not output_file: output_file = input_file + '.fsv_meta' with TempDirectory() as temp_dir: self._do_generate(input_file, output_file, temp_dir) def _do_generate(self, input_file, output_file, work_dir): # temporary files desc_file = os.path.join(work_dir, 'desc') merkletree_file = os.path.join(work_dir, 'merkletree') sig_file = os.path.join(work_dir, 'signature') # run the fsverity util to create the temporary files cmd = [self._fsverity_path] if self._signature == 'none': cmd.append('digest') cmd.append(input_file) else: cmd.append('sign') cmd.append(input_file) cmd.append(sig_file) # convert DER private key to PEM pem_key = os.path.join(work_dir, 'key.pem') key_cmd = ['openssl', 'pkcs8'] key_cmd.extend(['-inform', 'DER']) key_cmd.extend(['-in', self._key]) key_cmd.extend(['-nocrypt']) key_cmd.extend(['-out', pem_key]) subprocess.check_call(key_cmd) cmd.extend(['--key', pem_key]) cmd.extend(['--cert', self._cert]) cmd.extend(['--hash-alg', self._hash_alg]) cmd.extend(['--block-size', '4096']) cmd.extend(['--out-merkle-tree', merkletree_file]) cmd.extend(['--out-descriptor', desc_file]) subprocess.check_call(cmd, stdout=open(os.devnull, 'w')) with open(output_file, 'wb') as out: # 1. version out.write(pack('<I', 1)) # 2. fsverity_descriptor with open(desc_file, 'rb') as f: out.write(f.read()) # 3. signature SIG_TYPE_NONE = 0 SIG_TYPE_PKCS7 = 1 SIG_TYPE_RAW = 2 if self._signature == 'raw': out.write(pack('<I', SIG_TYPE_RAW)) sig = self._raw_signature(sig_file) out.write(pack('<I', len(sig))) out.write(sig) elif self._signature == 'pkcs7': with open(sig_file, 'rb') as f: out.write(pack('<I', SIG_TYPE_PKCS7)) sig = f.read() out.write(pack('<I', len(sig))) out.write(sig) else: out.write(pack('<I', SIG_TYPE_NONE)) # 4. merkle tree with open(merkletree_file, 'rb') as f: # merkle tree is placed at the next nearest page boundary to make # mmapping possible out.seek(next_page(out.tell())) out.write(f.read()) def next_page(n): """ Returns the next nearest page boundary from `n` """ PAGE_SIZE = 4096 return (n + PAGE_SIZE - 1) // PAGE_SIZE * PAGE_SIZE if __name__ == '__main__': p = argparse.ArgumentParser() p.add_argument( '--output', help='output file. If omitted, print to <INPUT>.fsv_meta', metavar='output', default=None) p.add_argument( 'input', help='input file to be signed') p.add_argument( '--key', help='PKCS#8 private key file in DER format') p.add_argument( '--cert', help='x509 certificate file in PEM format') p.add_argument( '--hash-alg', help='hash algorithm to use to build the merkle tree', choices=['sha256', 'sha512'], default='sha256') p.add_argument( '--signature', help='format for signature', choices=['none', 'raw', 'pkcs7'], default='none') p.add_argument( '--fsverity-path', help='path to the fsverity program', required=True) args = p.parse_args(sys.argv[1:]) generator = FSVerityMetadataGenerator(args.fsverity_path) generator.set_signature(args.signature) if args.signature == 'none': if args.key or args.cert: raise ValueError("When signature is none, key and cert can't be set") else: if not args.key or not args.cert: raise ValueError("To generate signature, key and cert must be set") generator.set_key(args.key) generator.set_cert(args.cert) generator.set_hash_alg(args.hash_alg) generator.generate(args.input, args.output) Loading
core/Makefile +21 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 $@ Loading Loading @@ -1672,6 +1684,11 @@ define generate-image-prop-dictionary $(if $(filter $(2),system),\ $(if $(INTERNAL_SYSTEM_OTHER_PARTITION_SIZE),$(hide) echo "system_other_size=$(INTERNAL_SYSTEM_OTHER_PARTITION_SIZE)" >> $(1)) $(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),\ Loading Loading @@ -2773,6 +2790,10 @@ endef 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 $(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,$@) Loading
core/product.mk +10 −0 Original line number Diff line number Diff line Loading @@ -440,6 +440,16 @@ _product_single_value_vars += PRODUCT_INSTALL_EXTRA_FLATTENED_APEXES # This option is only meant to be set by GSI products. _product_single_value_vars += PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT # If set, metadata files for the following artifacts will be generated. # - system/framework/*.jar # - system/framework/oat/<arch>/*.{oat,vdex,art} # - system/etc/boot-image.prof # - system/etc/dirty-image-objects # One fsverity metadata container file per one input file will be generated in # system.img, with a suffix ".fsv_meta". e.g. a container file for # "/system/framework/foo.jar" will be "system/framework/foo.jar.fsv_meta". _product_single_value_vars += PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA .KATI_READONLY := _product_single_value_vars _product_list_vars _product_var_list :=$= $(_product_single_value_vars) $(_product_list_vars) Loading
tools/releasetools/Android.bp +11 −0 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ python_defaults { ], libs: [ "releasetools_common", "releasetools_fsverity_metadata_generator", "releasetools_verity_utils", ], required: [ Loading Loading @@ -259,6 +260,16 @@ python_library_host { ], } python_library_host { name: "releasetools_fsverity_metadata_generator", srcs: [ "fsverity_metadata_generator.py", ], libs: [ "fsverity_digests_proto_python", ], } python_library_host { name: "releasetools_verity_utils", srcs: [ Loading
tools/releasetools/build_image.py +78 −1 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ Usage: build_image input_directory properties_file output_image \\ from __future__ import print_function import glob import logging import os import os.path Loading @@ -34,6 +35,9 @@ import sys import common import verity_utils from fsverity_digests_pb2 import FSVerityDigests from fsverity_metadata_generator import FSVerityMetadataGenerator logger = logging.getLogger(__name__) OPTIONS = common.OPTIONS Loading Loading @@ -447,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. Loading Loading @@ -475,6 +541,13 @@ def BuildImage(in_dir, prop_dict, out_file, target_out=None): elif fs_type.startswith("f2fs") and prop_dict.get("f2fs_compress") == "true": fs_spans_partition = False if "fsverity_generate_metadata" in prop_dict: 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. verity_image_builder = verity_utils.CreateVerityImageBuilder(prop_dict) Loading Loading @@ -589,7 +662,6 @@ def BuildImage(in_dir, prop_dict, out_file, target_out=None): if verity_image_builder: verity_image_builder.Build(out_file) def ImagePropFromGlobalDict(glob_dict, mount_point): """Build an image property dictionary from the global dictionary. Loading Loading @@ -725,6 +797,11 @@ def ImagePropFromGlobalDict(glob_dict, mount_point): copy_prop("system_root_image", "system_root_image") copy_prop("root_dir", "root_dir") 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") Loading
tools/releasetools/fsverity_metadata_generator.py 0 → 100644 +231 −0 Original line number Diff line number Diff line #!/usr/bin/env python # # Copyright 2021 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ `fsverity_metadata_generator` generates fsverity metadata and signature to a container file This actually is a simple wrapper around the `fsverity` program. A file is signed by the program which produces the PKCS#7 signature file, merkle tree file , and the fsverity_descriptor file. Then the files are packed into a single output file so that the information about the signing stays together. Currently, the output of this script is used by `fd_server` which is the host- side backend of an authfs filesystem. `fd_server` uses this file in case when the underlying filesystem (ext4, etc.) on the device doesn't support the fsverity feature natively in which case the information is read directly from the filesystem using ioctl. """ import argparse import os import re import shutil import subprocess import sys import tempfile from struct import * class TempDirectory(object): def __enter__(self): self.name = tempfile.mkdtemp() return self.name def __exit__(self, *unused): shutil.rmtree(self.name) class FSVerityMetadataGenerator: def __init__(self, fsverity_path): self._fsverity_path = fsverity_path # Default values for some properties self.set_hash_alg("sha256") self.set_signature('none') def set_key(self, key): self._key = key def set_cert(self, cert): self._cert = cert def set_hash_alg(self, hash_alg): self._hash_alg = hash_alg def set_signature(self, signature): self._signature = signature def _raw_signature(pkcs7_sig_file): """ Extracts raw signature from DER formatted PKCS#7 detached signature file Do that by parsing the ASN.1 tree to get the location of the signature in the file and then read the portion. """ # Note: there seems to be no public python API (even in 3p modules) that # provides direct access to the raw signature at this moment. So, `openssl # asn1parse` commandline tool is used instead. cmd = ['openssl', 'asn1parse'] cmd.extend(['-inform', 'DER']) cmd.extend(['-in', pkcs7_sig_file]) out = subprocess.check_output(cmd, universal_newlines=True) # The signature is the last element in the tree last_line = out.splitlines()[-1] m = re.search('(\d+):.*hl=\s*(\d+)\s*l=\s*(\d+)\s*.*OCTET STRING', last_line) if not m: raise RuntimeError("Failed to parse asn1parse output: " + out) offset = int(m.group(1)) header_len = int(m.group(2)) size = int(m.group(3)) with open(pkcs7_sig_file, 'rb') as f: 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: raise RuntimeError("key must be specified.") if not self._cert: raise RuntimeError("cert must be specified.") if not output_file: output_file = input_file + '.fsv_meta' with TempDirectory() as temp_dir: self._do_generate(input_file, output_file, temp_dir) def _do_generate(self, input_file, output_file, work_dir): # temporary files desc_file = os.path.join(work_dir, 'desc') merkletree_file = os.path.join(work_dir, 'merkletree') sig_file = os.path.join(work_dir, 'signature') # run the fsverity util to create the temporary files cmd = [self._fsverity_path] if self._signature == 'none': cmd.append('digest') cmd.append(input_file) else: cmd.append('sign') cmd.append(input_file) cmd.append(sig_file) # convert DER private key to PEM pem_key = os.path.join(work_dir, 'key.pem') key_cmd = ['openssl', 'pkcs8'] key_cmd.extend(['-inform', 'DER']) key_cmd.extend(['-in', self._key]) key_cmd.extend(['-nocrypt']) key_cmd.extend(['-out', pem_key]) subprocess.check_call(key_cmd) cmd.extend(['--key', pem_key]) cmd.extend(['--cert', self._cert]) cmd.extend(['--hash-alg', self._hash_alg]) cmd.extend(['--block-size', '4096']) cmd.extend(['--out-merkle-tree', merkletree_file]) cmd.extend(['--out-descriptor', desc_file]) subprocess.check_call(cmd, stdout=open(os.devnull, 'w')) with open(output_file, 'wb') as out: # 1. version out.write(pack('<I', 1)) # 2. fsverity_descriptor with open(desc_file, 'rb') as f: out.write(f.read()) # 3. signature SIG_TYPE_NONE = 0 SIG_TYPE_PKCS7 = 1 SIG_TYPE_RAW = 2 if self._signature == 'raw': out.write(pack('<I', SIG_TYPE_RAW)) sig = self._raw_signature(sig_file) out.write(pack('<I', len(sig))) out.write(sig) elif self._signature == 'pkcs7': with open(sig_file, 'rb') as f: out.write(pack('<I', SIG_TYPE_PKCS7)) sig = f.read() out.write(pack('<I', len(sig))) out.write(sig) else: out.write(pack('<I', SIG_TYPE_NONE)) # 4. merkle tree with open(merkletree_file, 'rb') as f: # merkle tree is placed at the next nearest page boundary to make # mmapping possible out.seek(next_page(out.tell())) out.write(f.read()) def next_page(n): """ Returns the next nearest page boundary from `n` """ PAGE_SIZE = 4096 return (n + PAGE_SIZE - 1) // PAGE_SIZE * PAGE_SIZE if __name__ == '__main__': p = argparse.ArgumentParser() p.add_argument( '--output', help='output file. If omitted, print to <INPUT>.fsv_meta', metavar='output', default=None) p.add_argument( 'input', help='input file to be signed') p.add_argument( '--key', help='PKCS#8 private key file in DER format') p.add_argument( '--cert', help='x509 certificate file in PEM format') p.add_argument( '--hash-alg', help='hash algorithm to use to build the merkle tree', choices=['sha256', 'sha512'], default='sha256') p.add_argument( '--signature', help='format for signature', choices=['none', 'raw', 'pkcs7'], default='none') p.add_argument( '--fsverity-path', help='path to the fsverity program', required=True) args = p.parse_args(sys.argv[1:]) generator = FSVerityMetadataGenerator(args.fsverity_path) generator.set_signature(args.signature) if args.signature == 'none': if args.key or args.cert: raise ValueError("When signature is none, key and cert can't be set") else: if not args.key or not args.cert: raise ValueError("To generate signature, key and cert must be set") generator.set_key(args.key) generator.set_cert(args.cert) generator.set_hash_alg(args.hash_alg) generator.generate(args.input, args.output)