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:
@@ -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-recovery-as-boot-load,$(kmd))),\
|
||||||
$(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-image-kernel-modules-dir,GENERIC_RAMDISK,$(TARGET_RAMDISK_OUT),,modules.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.
|
# Cert-to-package mapping. Used by the post-build signing tools.
|
||||||
# Use a macro to add newline to each echo command
|
# Use a macro to add newline to each echo command
|
||||||
@@ -575,6 +585,8 @@ $(APKCERTS_FILE):
|
|||||||
$(if $(PACKAGES.$(p).EXTERNAL_KEY),\
|
$(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),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),$@))))
|
$(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.
|
# In case value of PACKAGES is empty.
|
||||||
$(hide) touch $@
|
$(hide) touch $@
|
||||||
|
|
||||||
@@ -1672,6 +1684,11 @@ define generate-image-prop-dictionary
|
|||||||
$(if $(filter $(2),system),\
|
$(if $(filter $(2),system),\
|
||||||
$(if $(INTERNAL_SYSTEM_OTHER_PARTITION_SIZE),$(hide) echo "system_other_size=$(INTERNAL_SYSTEM_OTHER_PARTITION_SIZE)" >> $(1))
|
$(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 $(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))
|
$(call add-common-ro-flags-to-image-props,system,$(1))
|
||||||
)
|
)
|
||||||
$(if $(filter $(2),system_other),\
|
$(if $(filter $(2),system_other),\
|
||||||
@@ -2773,6 +2790,10 @@ endef
|
|||||||
ifeq ($(BOARD_AVB_ENABLE),true)
|
ifeq ($(BOARD_AVB_ENABLE),true)
|
||||||
$(BUILT_SYSTEMIMAGE): $(BOARD_AVB_SYSTEM_KEY_PATH)
|
$(BUILT_SYSTEMIMAGE): $(BOARD_AVB_SYSTEM_KEY_PATH)
|
||||||
endif
|
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)
|
$(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE)
|
||||||
$(call build-systemimage-target,$@)
|
$(call build-systemimage-target,$@)
|
||||||
|
|
||||||
|
@@ -440,6 +440,16 @@ _product_single_value_vars += PRODUCT_INSTALL_EXTRA_FLATTENED_APEXES
|
|||||||
# This option is only meant to be set by GSI products.
|
# This option is only meant to be set by GSI products.
|
||||||
_product_single_value_vars += PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT
|
_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
|
.KATI_READONLY := _product_single_value_vars _product_list_vars
|
||||||
_product_var_list :=$= $(_product_single_value_vars) $(_product_list_vars)
|
_product_var_list :=$= $(_product_single_value_vars) $(_product_list_vars)
|
||||||
|
|
||||||
|
@@ -50,6 +50,7 @@ python_defaults {
|
|||||||
],
|
],
|
||||||
libs: [
|
libs: [
|
||||||
"releasetools_common",
|
"releasetools_common",
|
||||||
|
"releasetools_fsverity_metadata_generator",
|
||||||
"releasetools_verity_utils",
|
"releasetools_verity_utils",
|
||||||
],
|
],
|
||||||
required: [
|
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 {
|
python_library_host {
|
||||||
name: "releasetools_verity_utils",
|
name: "releasetools_verity_utils",
|
||||||
srcs: [
|
srcs: [
|
||||||
|
@@ -24,6 +24,7 @@ Usage: build_image input_directory properties_file output_image \\
|
|||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import glob
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
@@ -34,6 +35,9 @@ import sys
|
|||||||
import common
|
import common
|
||||||
import verity_utils
|
import verity_utils
|
||||||
|
|
||||||
|
from fsverity_digests_pb2 import FSVerityDigests
|
||||||
|
from fsverity_metadata_generator import FSVerityMetadataGenerator
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
OPTIONS = common.OPTIONS
|
OPTIONS = common.OPTIONS
|
||||||
@@ -447,6 +451,68 @@ def BuildImageMkfs(in_dir, prop_dict, out_file, target_out, fs_config):
|
|||||||
|
|
||||||
return mkfs_output
|
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):
|
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.
|
"""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":
|
elif fs_type.startswith("f2fs") and prop_dict.get("f2fs_compress") == "true":
|
||||||
fs_spans_partition = False
|
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,
|
# Get a builder for creating an image that's to be verified by Verified Boot,
|
||||||
# or None if not applicable.
|
# or None if not applicable.
|
||||||
verity_image_builder = verity_utils.CreateVerityImageBuilder(prop_dict)
|
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:
|
if verity_image_builder:
|
||||||
verity_image_builder.Build(out_file)
|
verity_image_builder.Build(out_file)
|
||||||
|
|
||||||
|
|
||||||
def ImagePropFromGlobalDict(glob_dict, mount_point):
|
def ImagePropFromGlobalDict(glob_dict, mount_point):
|
||||||
"""Build an image property dictionary from the global dictionary.
|
"""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("system_root_image", "system_root_image")
|
||||||
copy_prop("root_dir", "root_dir")
|
copy_prop("root_dir", "root_dir")
|
||||||
copy_prop("root_fs_config", "root_fs_config")
|
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":
|
elif mount_point == "data":
|
||||||
# Copy the generic fs type first, override with specific one if available.
|
# Copy the generic fs type first, override with specific one if available.
|
||||||
copy_prop("flash_logical_block_size", "flash_logical_block_size")
|
copy_prop("flash_logical_block_size", "flash_logical_block_size")
|
||||||
|
231
tools/releasetools/fsverity_metadata_generator.py
Normal file
231
tools/releasetools/fsverity_metadata_generator.py
Normal 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)
|
Reference in New Issue
Block a user