Issue #88, Issue #75: ignore empty bootSig in boot V4

Issue #88: Android 12 has empty bootSig, while Android 13 doesn't, so we
    ignore this part duing re-packing
Issue #75: allow duplicated entry in CPIO
Also: replace slf4j-simple with logback
pull/94/head
cfig 3 years ago
parent b701884df2
commit 2d8c39b797
No known key found for this signature in database
GPG Key ID: B104C307F0FDABB7

@ -16,20 +16,6 @@ package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
python_binary_host {
name: "certify_bootimg",
defaults: ["mkbootimg_defaults"],
main: "certify_bootimg.py",
srcs: [
"certify_bootimg.py",
"generate_gki_certificate.py",
],
required: [
"avbtool",
"unpack_bootimg",
],
}
python_test_host {
name: "certify_bootimg_test",
defaults: ["mkbootimg_defaults"],

@ -25,7 +25,8 @@ import shutil
import subprocess
import tempfile
from generate_gki_certificate import generate_gki_certificate
from gki.generate_gki_certificate import generate_gki_certificate
from unpack_bootimg import unpack_bootimg
BOOT_SIGNATURE_SIZE = 16 * 1024
@ -33,14 +34,7 @@ BOOT_SIGNATURE_SIZE = 16 * 1024
def get_kernel(boot_img):
"""Extracts the kernel from |boot_img| and returns it."""
with tempfile.TemporaryDirectory() as unpack_dir:
unpack_bootimg_cmd = [
'unpack_bootimg',
'--boot_img', boot_img,
'--out', unpack_dir,
]
subprocess.run(unpack_bootimg_cmd, check=True,
stdout=subprocess.DEVNULL)
unpack_bootimg(boot_img, unpack_dir)
with open(os.path.join(unpack_dir, 'kernel'), 'rb') as kernel:
kernel_bytes = kernel.read()
assert len(kernel_bytes) > 0
@ -138,7 +132,7 @@ def get_avb_image_size(image):
return 0
def add_avb_footer(image, partition_size):
def add_avb_footer(image, partition_size, extra_footer_args):
"""Appends a AVB hash footer to the image."""
avbtool_cmd = ['avbtool', 'add_hash_footer', '--image', image,
@ -149,6 +143,7 @@ def add_avb_footer(image, partition_size):
else:
avbtool_cmd.extend(['--dynamic_partition_size'])
avbtool_cmd.extend(extra_footer_args)
subprocess.check_call(avbtool_cmd)
@ -166,6 +161,40 @@ def load_dict_from_file(path):
return d
def load_gki_info_file(gki_info_file, extra_args, extra_footer_args):
"""Loads extra arguments from the gki info file.
Args:
gki_info_file: path to a gki-info.txt.
extra_args: the extra arguments forwarded to avbtool when creating
the gki certificate.
extra_footer_args: the extra arguments forwarded to avbtool when
creating the avb footer.
"""
info_dict = load_dict_from_file(gki_info_file)
if 'certify_bootimg_extra_args' in info_dict:
extra_args.extend(
shlex.split(info_dict['certify_bootimg_extra_args']))
if 'certify_bootimg_extra_footer_args' in info_dict:
extra_footer_args.extend(
shlex.split(info_dict['certify_bootimg_extra_footer_args']))
def get_archive_name_and_format_for_shutil(path):
"""Returns archive name and format to shutil.make_archive() for the |path|.
e.g., returns ('/path/to/boot-img', 'gztar') if |path| is
'/path/to/boot-img.tar.gz'.
"""
for format_name, format_extensions, _ in shutil.get_unpack_formats():
for extension in format_extensions:
if path.endswith(extension):
return path[:-len(extension)], format_name
raise ValueError(f"Unsupported archive format: '{path}'")
def parse_cmdline():
"""Parse command-line options."""
parser = ArgumentParser(add_help=True)
@ -175,30 +204,50 @@ def parse_cmdline():
input_group.add_argument(
'--boot_img', help='path to the boot image to certify')
input_group.add_argument(
'--boot_img_zip', help='path to the boot-img-*.zip archive to certify')
'--boot_img_archive', help='path to the boot images archive to certify')
parser.add_argument('--algorithm', required=True,
help='signing algorithm for the certificate')
parser.add_argument('--key', required=True,
help='path to the RSA private key')
parser.add_argument('--gki_info',
help='path to a gki-info.txt to append additional'
'properties into the boot signature')
parser.add_argument('-o', '--output', required=True,
help='output file name')
# Optional args.
parser.add_argument('--extra_args', default=[], action='append',
help='extra arguments to be forwarded to avbtool')
parser.add_argument('--extra_footer_args', default=[], action='append',
help='extra arguments for adding the avb footer')
args = parser.parse_args()
if args.gki_info and args.boot_img_archive:
parser.error('--gki_info cannot be used with --boot_image_archive. '
'The gki_info file should be included in the archive.')
extra_args = []
for a in args.extra_args:
extra_args.extend(shlex.split(a))
args.extra_args = extra_args
extra_footer_args = []
for a in args.extra_footer_args:
extra_footer_args.extend(shlex.split(a))
args.extra_footer_args = extra_footer_args
if args.gki_info:
load_gki_info_file(args.gki_info,
args.extra_args,
args.extra_footer_args)
return args
def certify_bootimg(boot_img, output_img, algorithm, key, extra_args):
def certify_bootimg(boot_img, output_img, algorithm, key, extra_args,
extra_footer_args):
"""Certify a GKI boot image by generating and appending a boot_signature."""
with tempfile.TemporaryDirectory() as temp_dir:
boot_tmp = os.path.join(temp_dir, 'boot.tmp')
@ -208,40 +257,53 @@ def certify_bootimg(boot_img, output_img, algorithm, key, extra_args):
add_certificate(boot_tmp, algorithm, key, extra_args)
avb_partition_size = get_avb_image_size(boot_img)
add_avb_footer(boot_tmp, avb_partition_size)
add_avb_footer(boot_tmp, avb_partition_size, extra_footer_args)
# We're done, copy the temp image to the final output.
shutil.copy2(boot_tmp, output_img)
def certify_bootimg_zip(boot_img_zip, output_zip, algorithm, key, extra_args):
"""Similar to certify_bootimg(), but for a zip archive of boot images."""
with tempfile.TemporaryDirectory() as unzip_dir:
shutil.unpack_archive(boot_img_zip, unzip_dir)
def certify_bootimg_archive(boot_img_archive, output_archive,
algorithm, key, extra_args, extra_footer_args):
"""Similar to certify_bootimg(), but for an archive of boot images."""
with tempfile.TemporaryDirectory() as unpack_dir:
shutil.unpack_archive(boot_img_archive, unpack_dir)
info_dict = load_dict_from_file(os.path.join(unzip_dir, 'gki-info.txt'))
extra_args.extend(shlex.split(info_dict['certify_bootimg_extra_args']))
gki_info_file = os.path.join(unpack_dir, 'gki-info.txt')
if os.path.exists(gki_info_file):
load_gki_info_file(gki_info_file, extra_args, extra_footer_args)
for boot_img in glob.glob(os.path.join(unzip_dir, 'boot-*.img')):
for boot_img in glob.glob(os.path.join(unpack_dir, 'boot-*.img')):
print(f'Certifying {os.path.basename(boot_img)} ...')
certify_bootimg(boot_img=boot_img, output_img=boot_img,
algorithm=algorithm, key=key, extra_args=extra_args)
print(f'Making certified archive: {output_zip}')
archive_base_name = os.path.splitext(output_zip)[0]
shutil.make_archive(archive_base_name, 'zip', unzip_dir)
algorithm=algorithm, key=key, extra_args=extra_args,
extra_footer_args=extra_footer_args)
print(f'Making certified archive: {output_archive}')
archive_file_name, archive_format = (
get_archive_name_and_format_for_shutil(output_archive))
built_archive = shutil.make_archive(archive_file_name,
archive_format,
unpack_dir)
# shutil.make_archive() builds *.tar.gz when then |archive_format| is
# 'gztar'. However, the end user might specify |output_archive| with
# *.tgz. Renaming *.tar.gz to *.tgz for this case.
if built_archive != os.path.realpath(output_archive):
print(f'Renaming {built_archive} -> {output_archive} ...')
os.rename(built_archive, output_archive)
def main():
"""Parse arguments and certify the boot image."""
args = parse_cmdline()
if args.boot_img_zip:
certify_bootimg_zip(args.boot_img_zip, args.output, args.algorithm,
args.key, args.extra_args)
if args.boot_img_archive:
certify_bootimg_archive(args.boot_img_archive, args.output,
args.algorithm, args.key, args.extra_args,
args.extra_footer_args)
else:
certify_bootimg(args.boot_img, args.output, args.algorithm,
args.key, args.extra_args)
args.key, args.extra_args, args.extra_footer_args)
if __name__ == '__main__':

@ -68,18 +68,27 @@ def generate_test_boot_image(boot_img, kernel_size=4096, seed='kernel',
subprocess.check_call(avbtool_cmd)
def generate_test_boot_image_archive(output_zip, boot_img_info):
"""Generates a zip archive of test boot images.
def generate_test_boot_image_archive(archive_file_name, archive_format,
boot_img_info, gki_info=None):
"""Generates an archive of test boot images.
It also adds a file gki-info.txt, which contains additional settings for
for `certify_bootimg --extra_args`.
Args:
output_zip: the output zip archive, e.g., /path/to/boot-img.zip.
archive_file_name: the name of the archive file to create, including the
path, minus any format-specific extension.
archive_format: the |format| parameter for shutil.make_archive().
e.g., 'zip', 'tar', or 'gztar', etc.
boot_img_info: a list of (boot_image_name, kernel_size,
partition_size) tuples. e.g.,
[('boot-1.0.img', 4096, 4 * 1024),
('boot-2.0.img', 8192, 8 * 1024)].
gki_info: the file content to be written into 'gki-info.txt' in the
created archive.
Returns:
The full path of the created archive. e.g., /path/to/boot-img.tar.gz.
"""
with tempfile.TemporaryDirectory() as temp_out_dir:
for name, kernel_size, partition_size in boot_img_info:
@ -89,17 +98,14 @@ def generate_test_boot_image_archive(output_zip, boot_img_info):
seed=name,
avb_partition_size=partition_size)
gki_info = os.path.join(temp_out_dir, 'gki-info.txt')
with open(gki_info, 'w', encoding='utf-8') as f:
f.write('certify_bootimg_extra_args='
'--prop KERNEL_RELEASE:5.10.42'
'-android13-0-00544-ged21d463f856 '
'--prop BRANCH:android13-5.10-2022-05 '
'--prop BUILD_NUMBER:ab8295296 '
'--prop SPACE:"nice to meet you"\n')
if gki_info:
gki_info_path = os.path.join(temp_out_dir, 'gki-info.txt')
with open(gki_info_path, 'w', encoding='utf-8') as f:
f.write(gki_info)
archive_base_name = os.path.splitext(output_zip)[0]
shutil.make_archive(archive_base_name, 'zip', temp_out_dir)
return shutil.make_archive(archive_file_name,
archive_format,
temp_out_dir)
def has_avb_footer(image):
@ -177,10 +183,10 @@ def extract_boot_signatures(boot_img, output_dir):
boot_signature_bytes = boot_signature_bytes[next_signature_size:]
def extract_boot_archive_with_signatures(boot_img_zip, output_dir):
def extract_boot_archive_with_signatures(boot_img_archive, output_dir):
"""Extracts boot images and signatures of a boot images archive.
Suppose there are two boot images in |boot_img_zip|: boot-1.0.img
Suppose there are two boot images in |boot_img_archive|: boot-1.0.img
and boot-2.0.img. This function then extracts each boot-*.img and
their signatures as:
- |output_dir|/boot-1.0.img
@ -190,7 +196,7 @@ def extract_boot_archive_with_signatures(boot_img_zip, output_dir):
- |output_dir|/boot-2.0/boot_signature1
- |output_dir|/boot-2.0/boot_signature2
"""
shutil.unpack_archive(boot_img_zip, output_dir)
shutil.unpack_archive(boot_img_archive, output_dir)
for boot_img in glob.glob(os.path.join(output_dir, 'boot-*.img')):
img_name = os.path.splitext(os.path.basename(boot_img))[0]
signature_output_dir = os.path.join(output_dir, img_name)
@ -213,6 +219,197 @@ class CertifyBootimgTest(unittest.TestCase):
# C0103: invalid-name for maxDiff.
self.maxDiff = None # pylint: disable=C0103
# For AVB footers, we don't sign it so the Authentication block
# is zero bytes and the Algorithm is NONE. The footer will be
# replaced by device-specific settings when being incorporated into
# a device codebase. The footer here is just to pass some GKI
# pre-release test.
self._EXPECTED_AVB_FOOTER_BOOT_CERTIFIED = ( # pylint: disable=C0103
'Footer version: 1.0\n'
'Image size: 131072 bytes\n'
'Original image size: 24576 bytes\n'
'VBMeta offset: 24576\n'
'VBMeta size: 576 bytes\n'
'--\n'
'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n'
'Authentication Block: 0 bytes\n'
'Auxiliary Block: 320 bytes\n'
'Algorithm: NONE\n'
'Rollback Index: 0\n'
'Flags: 0\n'
'Rollback Index Location: 0\n'
"Release String: 'avbtool 1.2.0'\n"
'Descriptors:\n'
' Hash descriptor:\n'
' Image Size: 24576 bytes\n'
' Hash Algorithm: sha256\n'
' Partition Name: boot\n'
' Salt: a11ba11b\n'
' Digest: '
'c9b4ad78fae6f72f7eff939dee6078ed'
'8a75132e53f6c11ba1ec0f4b57f9eab0\n'
' Flags: 0\n'
" Prop: avb -> 'nice'\n"
" Prop: avb_space -> 'nice to meet you'\n"
)
self._EXPECTED_AVB_FOOTER_BOOT_CERTIFIED_2 = ( # pylint: disable=C0103
'Footer version: 1.0\n'
'Image size: 131072 bytes\n'
'Original image size: 24576 bytes\n'
'VBMeta offset: 24576\n'
'VBMeta size: 576 bytes\n'
'--\n'
'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n'
'Authentication Block: 0 bytes\n'
'Auxiliary Block: 320 bytes\n'
'Algorithm: NONE\n'
'Rollback Index: 0\n'
'Flags: 0\n'
'Rollback Index Location: 0\n'
"Release String: 'avbtool 1.2.0'\n"
'Descriptors:\n'
' Hash descriptor:\n'
' Image Size: 24576 bytes\n'
' Hash Algorithm: sha256\n'
' Partition Name: boot\n'
' Salt: a11ba11b\n'
' Digest: '
'ae2538e78b2a30b1112cede30d858a5f'
'6f8dc2a1b109dd4a7bb28124b77d2ab0\n'
' Flags: 0\n'
" Prop: avb -> 'nice'\n"
" Prop: avb_space -> 'nice to meet you'\n"
)
self._EXPECTED_AVB_FOOTER_WITH_GKI_INFO = ( # pylint: disable=C0103
'Footer version: 1.0\n'
'Image size: 131072 bytes\n'
'Original image size: 24576 bytes\n'
'VBMeta offset: 24576\n'
'VBMeta size: 704 bytes\n'
'--\n'
'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n'
'Authentication Block: 0 bytes\n'
'Auxiliary Block: 448 bytes\n'
'Algorithm: NONE\n'
'Rollback Index: 0\n'
'Flags: 0\n'
'Rollback Index Location: 0\n'
"Release String: 'avbtool 1.2.0'\n"
'Descriptors:\n'
' Hash descriptor:\n'
' Image Size: 24576 bytes\n'
' Hash Algorithm: sha256\n'
' Partition Name: boot\n'
' Salt: a11ba11b\n'
' Digest: '
'363d4f246a4a5e1bba8ba8b86f5eb0cf'
'9817e4e51663ba26edccf71c3861090a\n'
' Flags: 0\n'
" Prop: avb -> 'nice'\n"
" Prop: avb_space -> 'nice to meet you'\n"
" Prop: com.android.build.boot.os_version -> '13'\n"
" Prop: com.android.build.boot.security_patch -> '2022-05-05'\n"
)
self._EXPECTED_AVB_FOOTER_BOOT_1_0 = ( # pylint: disable=C0103
'Footer version: 1.0\n'
'Image size: 131072 bytes\n'
'Original image size: 28672 bytes\n'
'VBMeta offset: 28672\n'
'VBMeta size: 704 bytes\n'
'--\n'
'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n'
'Authentication Block: 0 bytes\n'
'Auxiliary Block: 448 bytes\n'
'Algorithm: NONE\n'
'Rollback Index: 0\n'
'Flags: 0\n'
'Rollback Index Location: 0\n'
"Release String: 'avbtool 1.2.0'\n"
'Descriptors:\n'
' Hash descriptor:\n'
' Image Size: 28672 bytes\n'
' Hash Algorithm: sha256\n'
' Partition Name: boot\n'
' Salt: a11ba11b\n'
' Digest: '
'634e60e08f5b83842c70fa0efa05de87'
'643cd75357f06eff9acc3d1f93e26795\n'
' Flags: 0\n'
" Prop: avb -> 'nice'\n"
" Prop: avb_space -> 'nice to meet you'\n"
" Prop: com.android.build.boot.os_version -> '13'\n"
" Prop: com.android.build.boot.security_patch -> '2022-05-05'\n"
)
self._EXPECTED_AVB_FOOTER_BOOT_2_0 = ( # pylint: disable=C0103
'Footer version: 1.0\n'
'Image size: 262144 bytes\n'
'Original image size: 36864 bytes\n'
'VBMeta offset: 36864\n'
'VBMeta size: 704 bytes\n'
'--\n'
'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n'
'Authentication Block: 0 bytes\n'
'Auxiliary Block: 448 bytes\n'
'Algorithm: NONE\n'
'Rollback Index: 0\n'
'Flags: 0\n'
'Rollback Index Location: 0\n'
"Release String: 'avbtool 1.2.0'\n"
'Descriptors:\n'
' Hash descriptor:\n'
' Image Size: 36864 bytes\n'
' Hash Algorithm: sha256\n'
' Partition Name: boot\n'
' Salt: a11ba11b\n'
' Digest: '
'f9bb362d8d0e6559f9f8f42eeaf4da9f'
'0fca6093de74ac406f76719fd0b20102\n'
' Flags: 0\n'
" Prop: avb -> 'nice'\n"
" Prop: avb_space -> 'nice to meet you'\n"
" Prop: com.android.build.boot.os_version -> '13'\n"
" Prop: com.android.build.boot.security_patch -> '2022-05-05'\n"
)
self._EXPECTED_AVB_FOOTER_BOOT_3_0 = ( # pylint: disable=C0103
'Footer version: 1.0\n'
'Image size: 131072 bytes\n'
'Original image size: 28672 bytes\n'
'VBMeta offset: 28672\n'
'VBMeta size: 576 bytes\n'
'--\n'
'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n'
'Authentication Block: 0 bytes\n'
'Auxiliary Block: 320 bytes\n'
'Algorithm: NONE\n'
'Rollback Index: 0\n'
'Flags: 0\n'
'Rollback Index Location: 0\n'
"Release String: 'avbtool 1.2.0'\n"
'Descriptors:\n'
' Hash descriptor:\n'
' Image Size: 28672 bytes\n'
' Hash Algorithm: sha256\n'
' Partition Name: boot\n'
' Salt: a11ba11b\n'
' Digest: '
'fb0326a78b3794c79fad414d10f8d69a'
'86a0da49e5320bd5b4fc09272cb2cad9\n'
' Flags: 0\n'
" Prop: avb -> 'nice'\n"
" Prop: avb_space -> 'nice to meet you'\n"
)
self._EXPECTED_BOOT_SIGNATURE_RSA2048 = ( # pylint: disable=C0103
'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n'
@ -317,6 +514,68 @@ class CertifyBootimgTest(unittest.TestCase):
" Prop: space -> 'nice to meet you'\n"
)
self._EXPECTED_BOOT_SIGNATURE_WITH_GKI_INFO = ( # pylint: disable=C0103
'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n'
'Authentication Block: 576 bytes\n'
'Auxiliary Block: 1600 bytes\n'
'Public key (sha1): '
'2597c218aae470a130f61162feaae70afd97f011\n'
'Algorithm: SHA256_RSA4096\n' # RSA4096
'Rollback Index: 0\n'
'Flags: 0\n'
'Rollback Index Location: 0\n'
"Release String: 'avbtool 1.2.0'\n"
'Descriptors:\n'
' Hash descriptor:\n'
' Image Size: 8192 bytes\n'
' Hash Algorithm: sha256\n'
' Partition Name: boot\n' # boot
' Salt: d00df00d\n'
' Digest: '
'faf1da72a4fba97ddab0b8f7a410db86'
'8fb72392a66d1440ff8bff490c73c771\n'
' Flags: 0\n'
" Prop: gki -> 'nice'\n"
" Prop: space -> 'nice to meet you'\n"
" Prop: KERNEL_RELEASE -> '5.10.42-android13-0-00544-"
"ged21d463f856'\n"
" Prop: BRANCH -> 'android13-5.10-2022-05'\n"
" Prop: BUILD_NUMBER -> 'ab8295296'\n"
" Prop: GKI_INFO -> 'added here'\n"
)
self._EXPECTED_KERNEL_SIGNATURE_WITH_GKI_INFO = (# pylint: disable=C0103
'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n'
'Authentication Block: 576 bytes\n'
'Auxiliary Block: 1600 bytes\n'
'Public key (sha1): '
'2597c218aae470a130f61162feaae70afd97f011\n'
'Algorithm: SHA256_RSA4096\n' # RSA4096
'Rollback Index: 0\n'
'Flags: 0\n'
'Rollback Index Location: 0\n'
"Release String: 'avbtool 1.2.0'\n"
'Descriptors:\n'
' Hash descriptor:\n'
' Image Size: 4096 bytes\n'
' Hash Algorithm: sha256\n'
' Partition Name: generic_kernel\n' # generic_kernel
' Salt: d00df00d\n'
' Digest: '
'762c877f3af0d50a4a4fbc1385d5c7ce'
'52a1288db74b33b72217d93db6f2909f\n'
' Flags: 0\n'
" Prop: gki -> 'nice'\n"
" Prop: space -> 'nice to meet you'\n"
" Prop: KERNEL_RELEASE -> '5.10.42-android13-0-00544-"
"ged21d463f856'\n"
" Prop: BRANCH -> 'android13-5.10-2022-05'\n"
" Prop: BUILD_NUMBER -> 'ab8295296'\n"
" Prop: GKI_INFO -> 'added here'\n"
)
self._EXPECTED_BOOT_1_0_SIGNATURE1_RSA4096 = ( # pylint: disable=C0103
'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n'
@ -441,6 +700,58 @@ class CertifyBootimgTest(unittest.TestCase):
" Prop: SPACE -> 'nice to meet you'\n"
)
self._EXPECTED_BOOT_3_0_SIGNATURE1_RSA4096 = ( # pylint: disable=C0103
'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n'
'Authentication Block: 576 bytes\n'
'Auxiliary Block: 1344 bytes\n'
'Public key (sha1): '
'2597c218aae470a130f61162feaae70afd97f011\n'
'Algorithm: SHA256_RSA4096\n' # RSA4096
'Rollback Index: 0\n'
'Flags: 0\n'
'Rollback Index Location: 0\n'
"Release String: 'avbtool 1.2.0'\n"
'Descriptors:\n'
' Hash descriptor:\n'
' Image Size: 12288 bytes\n'
' Hash Algorithm: sha256\n'
' Partition Name: boot\n' # boot
' Salt: d00df00d\n'
' Digest: '
'9b9cd845a367d7fc9b61d6ac02b0e7c9'
'dc3d3b219abf60dd6e19359f0353c917\n'
' Flags: 0\n'
" Prop: gki -> 'nice'\n"
" Prop: space -> 'nice to meet you'\n"
)
self._EXPECTED_BOOT_3_0_SIGNATURE2_RSA4096 = ( # pylint: disable=C0103
'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n'
'Authentication Block: 576 bytes\n'
'Auxiliary Block: 1344 bytes\n'
'Public key (sha1): '
'2597c218aae470a130f61162feaae70afd97f011\n'
'Algorithm: SHA256_RSA4096\n' # RSA4096
'Rollback Index: 0\n'
'Flags: 0\n'
'Rollback Index Location: 0\n'
"Release String: 'avbtool 1.2.0'\n"
'Descriptors:\n'
' Hash descriptor:\n'
' Image Size: 8192 bytes\n'
' Hash Algorithm: sha256\n'
' Partition Name: generic_kernel\n' # generic_kernel
' Salt: d00df00d\n'
' Digest: '
'0cd7d331ed9b32dcd92f00e2cac75595'
'52199170afe788a8fcf1954f9ea072d0\n'
' Flags: 0\n'
" Prop: gki -> 'nice'\n"
" Prop: space -> 'nice to meet you'\n"
)
def _test_boot_signatures(self, signatures_dir, expected_signatures_info):
"""Tests the info of each boot signature under the signature directory.
@ -527,6 +838,8 @@ class CertifyBootimgTest(unittest.TestCase):
'--key', './testdata/testkey_rsa2048.pem',
'--extra_args', '--prop gki:nice '
'--prop space:"nice to meet you"',
'--extra_footer_args', '--salt a11ba11b --prop avb:nice '
'--prop avb_space:"nice to meet you"',
'--output', boot_certified_img,
]
subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir)
@ -535,7 +848,13 @@ class CertifyBootimgTest(unittest.TestCase):
self.assertTrue(has_avb_footer(boot_certified_img))
self.assertEqual(os.path.getsize(boot_img),
os.path.getsize(boot_certified_img))
# Checks the content in the AVB footer.
self._test_boot_signatures(
temp_out_dir,
{'boot-certified.img':
self._EXPECTED_AVB_FOOTER_BOOT_CERTIFIED})
# Checks the content in the GKI certificate.
extract_boot_signatures(boot_certified_img, temp_out_dir)
self._test_boot_signatures(
temp_out_dir,
@ -552,6 +871,8 @@ class CertifyBootimgTest(unittest.TestCase):
'--key', './testdata/testkey_rsa4096.pem',
'--extra_args', '--prop gki:nice '
'--prop space:"nice to meet you"',
'--extra_footer_args', '--salt a11ba11b --prop avb:nice '
'--prop avb_space:"nice to meet you"',
'--output', boot_certified2_img,
]
subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir)
@ -560,13 +881,77 @@ class CertifyBootimgTest(unittest.TestCase):
self.assertTrue(has_avb_footer(boot_certified2_img))
self.assertEqual(os.path.getsize(boot_certified_img),
os.path.getsize(boot_certified2_img))
# Checks the content in the AVB footer.
self._test_boot_signatures(
temp_out_dir,
{'boot-certified2.img':
self._EXPECTED_AVB_FOOTER_BOOT_CERTIFIED_2})
# Checks the content in the GKI certificate.
extract_boot_signatures(boot_certified2_img, temp_out_dir)
self._test_boot_signatures(
temp_out_dir,
{'boot_signature1': self._EXPECTED_BOOT_SIGNATURE_RSA4096,
'boot_signature2': self._EXPECTED_KERNEL_SIGNATURE_RSA4096})
def test_certify_bootimg_with_gki_info(self):
"""Tests certify_bootimg with --gki_info."""
with tempfile.TemporaryDirectory() as temp_out_dir:
boot_img = os.path.join(temp_out_dir, 'boot.img')
generate_test_boot_image(boot_img=boot_img,
avb_partition_size=128 * 1024)
self.assertTrue(has_avb_footer(boot_img))
gki_info = ('certify_bootimg_extra_args='
'--prop KERNEL_RELEASE:5.10.42'
'-android13-0-00544-ged21d463f856 '
'--prop BRANCH:android13-5.10-2022-05 '
'--prop BUILD_NUMBER:ab8295296 '
'--prop GKI_INFO:"added here"\n'
'certify_bootimg_extra_footer_args='
'--prop com.android.build.boot.os_version:13 '
'--prop com.android.build.boot.security_patch:'
'2022-05-05\n')
gki_info_path = os.path.join(temp_out_dir, 'gki-info.txt')
with open(gki_info_path, 'w', encoding='utf-8') as f:
f.write(gki_info)
# Certifies the boot image with --gki_info.
boot_certified_img = os.path.join(temp_out_dir,
'boot-certified.img')
certify_bootimg_cmds = [
'certify_bootimg',
'--boot_img', boot_img,
'--algorithm', 'SHA256_RSA4096',
'--key', './testdata/testkey_rsa4096.pem',
'--extra_args', '--prop gki:nice '
'--prop space:"nice to meet you"',
'--extra_footer_args', '--salt a11ba11b --prop avb:nice '
'--prop avb_space:"nice to meet you"',
'--gki_info', gki_info_path,
'--output', boot_certified_img,
]
subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir)
# Checks an AVB footer exists and the image size remains.
self.assertTrue(has_avb_footer(boot_certified_img))
self.assertEqual(os.path.getsize(boot_img),
os.path.getsize(boot_certified_img))
# Checks the content in the AVB footer.
self._test_boot_signatures(
temp_out_dir,
{'boot-certified.img': self._EXPECTED_AVB_FOOTER_WITH_GKI_INFO})
# Checks the content in the GKI certificate.
extract_boot_signatures(boot_certified_img, temp_out_dir)
self._test_boot_signatures(
temp_out_dir,
{'boot_signature1':
self._EXPECTED_BOOT_SIGNATURE_WITH_GKI_INFO,
'boot_signature2':
self._EXPECTED_KERNEL_SIGNATURE_WITH_GKI_INFO})
def test_certify_bootimg_exceed_size(self):
"""Tests the boot signature size exceeded max size of the signature."""
with tempfile.TemporaryDirectory() as temp_out_dir:
@ -597,30 +982,44 @@ class CertifyBootimgTest(unittest.TestCase):
err.stderr)
def test_certify_bootimg_archive(self):
"""Tests certify_bootimg for a boot-img.zip."""
"""Tests certify_bootimg for a boot images archive.."""
with tempfile.TemporaryDirectory() as temp_out_dir:
boot_img_zip = os.path.join(temp_out_dir, 'boot-img.zip')
generate_test_boot_image_archive(
boot_img_zip,
boot_img_archive_name = os.path.join(temp_out_dir, 'boot-img')
gki_info = ('certify_bootimg_extra_args='
'--prop KERNEL_RELEASE:5.10.42'
'-android13-0-00544-ged21d463f856 '
'--prop BRANCH:android13-5.10-2022-05 '
'--prop BUILD_NUMBER:ab8295296 '
'--prop SPACE:"nice to meet you"\n'
'certify_bootimg_extra_footer_args='
'--prop com.android.build.boot.os_version:13 '
'--prop com.android.build.boot.security_patch:'
'2022-05-05\n')
boot_img_archive_path = generate_test_boot_image_archive(
boot_img_archive_name,
'gztar',
# A list of (boot_img_name, kernel_size, partition_size).
[('boot-1.0.img', 8 * 1024, 128 * 1024),
('boot-2.0.img', 16 * 1024, 256 * 1024)])
('boot-2.0.img', 16 * 1024, 256 * 1024)],
gki_info)
# Certify the boot image archive, with a RSA4096 key.
boot_certified_img_zip = os.path.join(temp_out_dir,
'boot-certified-img.zip')
boot_certified_img_archive = os.path.join(
temp_out_dir, 'boot-certified-img.tar.gz')
certify_bootimg_cmds = [
'certify_bootimg',
'--boot_img_zip', boot_img_zip,
'--boot_img_archive', boot_img_archive_path,
'--algorithm', 'SHA256_RSA4096',
'--key', './testdata/testkey_rsa4096.pem',
'--extra_args', '--prop gki:nice '
'--prop space:"nice to meet you"',
'--output', boot_certified_img_zip,
'--extra_footer_args', '--salt a11ba11b --prop avb:nice '
'--prop avb_space:"nice to meet you"',
'--output', boot_certified_img_archive,
]
subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir)
extract_boot_archive_with_signatures(boot_certified_img_zip,
extract_boot_archive_with_signatures(boot_certified_img_archive,
temp_out_dir)
# Checks an AVB footer exists and the image size remains.
@ -632,6 +1031,13 @@ class CertifyBootimgTest(unittest.TestCase):
self.assertTrue(has_avb_footer(boot_2_img))
self.assertEqual(os.path.getsize(boot_2_img), 256 * 1024)
# Checks the content in the AVB footer.
self._test_boot_signatures(
temp_out_dir,
{'boot-1.0.img': self._EXPECTED_AVB_FOOTER_BOOT_1_0,
'boot-2.0.img': self._EXPECTED_AVB_FOOTER_BOOT_2_0})
# Checks the content in the GKI certificate.
self._test_boot_signatures(
temp_out_dir,
{'boot-1.0/boot_signature1':
@ -643,6 +1049,81 @@ class CertifyBootimgTest(unittest.TestCase):
'boot-2.0/boot_signature2':
self._EXPECTED_BOOT_2_0_SIGNATURE2_RSA4096})
def test_certify_bootimg_archive_without_gki_info(self):
"""Tests certify_bootimg for a boot images archive."""
with tempfile.TemporaryDirectory() as temp_out_dir:
boot_img_archive_name = os.path.join(temp_out_dir, 'boot-img')
# Checks ceritfy_bootimg works for a boot images archive without a
# gki-info.txt. Using *.zip -> *.tar.
boot_img_archive_path = generate_test_boot_image_archive(
boot_img_archive_name,
'zip',
# A list of (boot_img_name, kernel_size, partition_size).
[('boot-3.0.img', 8 * 1024, 128 * 1024)],
gki_info=None)
# Certify the boot image archive, with a RSA4096 key.
boot_certified_img_archive = os.path.join(
temp_out_dir, 'boot-certified-img.tar')
certify_bootimg_cmds = [
'certify_bootimg',
'--boot_img_archive', boot_img_archive_path,
'--algorithm', 'SHA256_RSA4096',
'--key', './testdata/testkey_rsa4096.pem',
'--extra_args', '--prop gki:nice '
'--prop space:"nice to meet you"',
'--extra_footer_args', '--salt a11ba11b --prop avb:nice '
'--prop avb_space:"nice to meet you"',
'--output', boot_certified_img_archive,
]
subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir)
# Checks ceritfy_bootimg works for a boot images archive with a
# special gki-info.txt. Using *.tar -> *.tgz.
boot_img_archive_path = generate_test_boot_image_archive(
boot_img_archive_name,
'tar',
# A list of (boot_img_name, kernel_size, partition_size).
[('boot-3.0.img', 8 * 1024, 128 * 1024)],
gki_info='a=b\n'
'c=d\n')
# Certify the boot image archive, with a RSA4096 key.
boot_certified_img_archive2 = os.path.join(
temp_out_dir, 'boot-certified-img.tgz')
certify_bootimg_cmds = [
'certify_bootimg',
'--boot_img_archive', boot_img_archive_path,
'--algorithm', 'SHA256_RSA4096',
'--key', './testdata/testkey_rsa4096.pem',
'--extra_args', '--prop gki:nice '
'--prop space:"nice to meet you"',
'--extra_footer_args', '--salt a11ba11b --prop avb:nice '
'--prop avb_space:"nice to meet you"',
'--output', boot_certified_img_archive2,
]
subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir)
extract_boot_archive_with_signatures(boot_certified_img_archive2,
temp_out_dir)
# Checks an AVB footer exists and the image size remains.
boot_3_img = os.path.join(temp_out_dir, 'boot-3.0.img')
self.assertTrue(has_avb_footer(boot_3_img))
self.assertEqual(os.path.getsize(boot_3_img), 128 * 1024)
# Checks the content in the AVB footer.
self._test_boot_signatures(
temp_out_dir,
{'boot-3.0.img': self._EXPECTED_AVB_FOOTER_BOOT_3_0})
# Checks the content in the GKI certificate.
self._test_boot_signatures(
temp_out_dir,
{'boot-3.0/boot_signature1':
self._EXPECTED_BOOT_3_0_SIGNATURE1_RSA4096,
'boot-3.0/boot_signature2':
self._EXPECTED_BOOT_3_0_SIGNATURE2_RSA4096})
# I don't know how, but we need both the logger configuration and verbosity
# level > 2 to make atest work. And yes this line needs to be at the very top

@ -34,8 +34,7 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("cc.cfig:io:0.2")
implementation("org.slf4j:slf4j-simple:1.7.36")
implementation("org.slf4j:slf4j-api:1.7.36")
implementation("ch.qos.logback:logback-classic:1.2.11")
implementation("com.fasterxml.jackson.core:jackson-annotations:2.13.1")
implementation("com.fasterxml.jackson.core:jackson-databind:2.13.1")
implementation("com.google.guava:guava:31.0.1-jre")

@ -223,18 +223,6 @@ class Common {
return md.digest()
}
fun assertFileEquals(file1: String, file2: String) {
val hash1 = hashFileAndSize(file1)
val hash2 = hashFileAndSize(file2)
log.info("$file1 hash ${Helper.toHexString(hash1)}, $file2 hash ${Helper.toHexString(hash2)}")
if (hash1.contentEquals(hash2)) {
log.info("Hash verification passed: ${Helper.toHexString(hash1)}")
} else {
log.error("Hash verification failed")
throw UnknownError("Do not know why hash verification fails, maybe a bug")
}
}
//using mkbootfs
fun packRootfs(rootDir: String, ramdiskGz: String, osMajor: Int = 10) {
val mkbootfs = String.format(Locale.getDefault(), Helper.prop("mkbootfsBin"), osMajor)

@ -69,7 +69,7 @@ class Signer {
File("$output.clear").copyTo(File("$output.signed2"), overwrite = true)
DefaultExecutor().execute(this)
}
Common.assertFileEquals("$output.signed", "$output.signed2")
Helper.assertFileEquals("$output.signed", "$output.signed2")
//TODO: decide what to verify
//Parser.verifyAVBIntegrity(cfg.info.output + ".signed", avbtool)
//Parser.verifyAVBIntegrity(cfg.info.output + ".signed2", avbtool)

@ -149,8 +149,15 @@ class AndroidCpio {
else -> {
//Issue #75: https://github.com/cfig/Android_boot_image_editor/issues/75
//Reason: cpio may have multiple entries with the same name, that's caused by man-made errors
throw IllegalArgumentException("${entry.name} has multiple exact-match fsConfig, " +
"check https://github.com/cfig/Android_boot_image_editor/issues/75")
val msg = "(${entry.name} has multiple exact-match fsConfig, " +
"check https://github.com/cfig/Android_boot_image_editor/issues/75"
errLog.warn("IllegalArgumentException$msg")
if (Helper.prop("config.allow_cpio_duplicate") == "true") {
log.warn("IllegalArgumentException$msg")
entry.statMode = itemConfig[0].statMode
} else {
throw IllegalArgumentException(msg)
}
}
}
}
@ -198,6 +205,7 @@ class AndroidCpio {
companion object {
private val log = LoggerFactory.getLogger(AndroidCpio::class.java)
private val errLog = LoggerFactory.getLogger("uiderrors")
private val PERM_MASK = java.lang.Long.valueOf("777", 8)
fun decompressCPIO(cpioFile: String, outDir: String, fileList: String? = null) {
run { //clean up

@ -437,7 +437,7 @@ data class BootV2(
DefaultExecutor().execute(this)
}
Common.assertFileEquals("${info.output}.clear", "${info.output}.google")
Helper.assertFileEquals("${info.output}.clear", "${info.output}.google")
return this
}

@ -456,7 +456,7 @@ data class BootV2Dialects(
DefaultExecutor().execute(this)
}
Common.assertFileEquals("${info.output}.clear", "${info.output}.google")
Helper.assertFileEquals("${info.output}.clear", "${info.output}.google")
return this
}

@ -44,6 +44,7 @@ data class BootV3(
) {
companion object {
private val log = LoggerFactory.getLogger(BootV3::class.java)
private val errLog = LoggerFactory.getLogger("uiderrors")
private val mapper = ObjectMapper()
private val workDir = Helper.prop("workDir")
@ -123,6 +124,16 @@ data class BootV3(
//header
FileOutputStream(this.info.output + ".clear", false).use { fos ->
//trim bootSig if it's not parsable
//https://github.com/cfig/Android_boot_image_editor/issues/88
File(Avb.getJsonFileName(this.bootSignature.file)).let { bootSigJson ->
if (!bootSigJson.exists()) {
errLog.info(
"erase unparsable boot signature in header. Refer to https://github.com/cfig/Android_boot_image_editor/issues/88"
)
this.info.signatureSize = 0
}
}
val encodedHeader = this.toHeader().encode()
fos.write(encodedHeader)
fos.write(
@ -160,10 +171,14 @@ data class BootV3(
readBackBootSig.auxBlob!!.hashDescriptors.get(0).update(this.info.output + ".clear")
bootSigBytes = readBackBootSig.encodePadded()
}
if (this.info.signatureSize > 0) {
//write V4 data
FileOutputStream("${this.info.output}.clear", true).use { fos ->
fos.write(bootSigBytes)
}
} else {
errLog.info("ignore bootsig for v4 boot.img")
}
}
//google way
@ -172,7 +187,7 @@ data class BootV3(
DefaultExecutor().execute(it)
}
C.assertFileEquals(this.info.output + ".clear", this.info.output + ".google")
Helper.assertFileEquals(this.info.output + ".clear", this.info.output + ".google")
return this
}

@ -305,7 +305,7 @@ data class VendorBoot(
DefaultExecutor().execute(it)
}
C.assertFileEquals(this.info.output + ".clear", this.info.output + ".google")
Helper.assertFileEquals(this.info.output + ".clear", this.info.output + ".google")
return this
}

@ -10,3 +10,4 @@ kernelExtracter = aosp/build/tools/extract_kernel.py
mkbootimg = aosp/system/tools/mkbootimg/mkbootimg.py
dtboMaker = aosp/system/libufdt/utils/src/mkdtboimg.py
payloadDir = build/payload/
config.allow_cpio_duplicate = true

@ -0,0 +1,21 @@
<configuration debug="false">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="SOME_ERRORS" class="ch.qos.logback.core.FileAppender">
<file>uiderrors</file>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
</encoder>
</appender>
<logger name="uiderrors" level="DEBUG" additivity="false">
<appender-ref ref="SOME_ERRORS"/>
</logger>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>

@ -1,5 +0,0 @@
org.slf4j.simpleLogger.defaultLogLevel = info
org.slf4j.simpleLogger.showDateTime = true
org.slf4j.simpleLogger.dateTimeFormat = HH:mm:ss:SSS
org.slf4j.simpleLogger.showThreadName = false
org.slf4j.simpleLogger.showShortLogName = true

@ -1 +0,0 @@
org.slf4j.simpleLogger.defaultLogLevel = debug

@ -35,8 +35,7 @@ dependencies {
implementation("cc.cfig:io:0.2")
implementation("com.google.guava:guava:31.0.1-jre")
implementation("org.slf4j:slf4j-api:1.7.32")
implementation("org.slf4j:slf4j-simple:1.7.32")
implementation("ch.qos.logback:logback-classic:1.2.11")
implementation("org.apache.commons:commons-exec:1.3")
implementation("org.bouncycastle:bcprov-jdk15on:1.69")
implementation("org.bouncycastle:bcpkix-jdk15on:1.69") //org.bouncycastle.pkcs

@ -37,7 +37,7 @@ def deleteIfExists(inFile):
def cleanUp():
log.info("clean up ...")
shutil.rmtree("build", ignore_errors = True)
shutil.rmtree("build/unzip_boot", ignore_errors = True)
[deleteIfExists(item) for item in [
"boot.img", "boot.img.clear", "boot.img.google", "boot.img.signed", "boot.img.signed2",
"recovery.img", "recovery.img.clear", "recovery.img.google", "recovery.img.signed", "recovery.img.signed2",
@ -165,6 +165,9 @@ def main():
verifySingleDir(resDir2, "issue_83")
# Issue 86: vendor_boot with vrt and board name
verifySingleDir(resDir2, "issue_86")
# Issue 88: boot image V4 without boot signature,
# and Issue 75: allow duplicated entry in cpio
verifySingleDir(resDir2, "issue_88")
log.info(successLogo)

@ -1 +1 @@
Subproject commit b4c46ec5b4e9da09a1142058d63b054e9fd2cddc
Subproject commit ce834dbe0bb8b307b45480d6724a33ee928bbaff
Loading…
Cancel
Save