Merge changes from topic "fsverity_digest" am: 866d824328 am: f90fb54430

Original change: https://android-review.googlesource.com/c/platform/build/+/1855993

Change-Id: I90c59cf7900eda8f903852c6de77868960cdaa01
This commit is contained in:
Treehugger Robot
2021-11-12 07:02:16 +00:00
committed by Automerger Merge Worker
5 changed files with 351 additions and 1 deletions

View File

@@ -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 $@
@@ -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),\
@@ -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,$@)

View File

@@ -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)

View File

@@ -50,6 +50,7 @@ python_defaults {
],
libs: [
"releasetools_common",
"releasetools_fsverity_metadata_generator",
"releasetools_verity_utils",
],
required: [
@@ -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: [

View File

@@ -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
@@ -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
@@ -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.
@@ -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)
@@ -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.
@@ -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")

View File

@@ -0,0 +1,231 @@
#!/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)