partially spupport boot signature

Android 12: 4K boot signature
Android 13: 16K boot signature
pull/94/head
cfig 3 years ago
parent 3df4ec6fc1
commit 0a342363de
No known key found for this signature in database
GPG Key ID: B104C307F0FDABB7

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA3fDgwU4JKVRHhAfofi/g8daTNplB2mTJCX9fIMy9FnZDXNij
1zijRQ8HKbt3bAGImQvb3GxSV4M5eIdiLDUF7RsUpE7K+s939i/AaTtcuyqimQbJ
QjP9emTsgngHzuKWMg1mwlRZYDfdv62zIQmZcbM9a0CZE36hAYvEBiDB8qT4ob++
godGAx3rpF2Wi7mhIYDINvkCw8/16Qi9CZgvOUrEolt3mz8Sps41z9j7YAsPbAa8
fg7dUu61s6NkZEykl4G67loOaf7h+SyP//LpFZ0gV+STZ+EMGofL0SXb8A+hdIYE
QxsnKUYo8e+GaQg92FLxVZqcfyG3AZuMB04R1QIDAQABAoIBAQDGj3/1UaSepjlJ
ZW3an2lH1Cpm2ZxyEGNQLPVluead1vaTdXq3zYM9AKHu8zp3lbOpAVQVk4/jnZJo
Q+9QD6waonTIP3oYBE+WIMirHSHsjctkzw52PV9VBkAWxd5ueIfZheXejGpdy/2H
RJcTQqxWbf7QGr4ZE9xmLq4UsW/zbXwy8qGEp9eMQIIaWBua43FkqmWYLSnVFVJI
Gl8mfVJctLNSZHhS3tKiV8up6NxZlDjO8o7kYVFCkv0xJ9yzQNBc3P2MEmvfZ06D
QnimHBqSxr0M9X6hqP43CnqtCbpsHS8A12Dm4l6fkXfkrAY0UNrEaCSDb8aN7TEc
7bc1MB4NAoGBAPK7xSuvQE9CH05Iy+G6mEQTtNmpfcQosqhi6dF60h4bqlkeGzUu
gF/PKHwwffHAxQSv4V831P3A/IoJFa9IFkg218mYPNfzpj4vJA4aNCDp+SYZAIYm
h6hMOmuByI97wds2yCBGt4mP0eow5B3A1b3UQeqW6LVSuobZ22QVlSk/AoGBAOoS
L82yda9hUa7vuXtqTraf9EGjSXhyjoPqWxa+a1ooI9l24f7mokS5Iof+a/SLfPUj
pwj8eOeOZksjAaWJIdrRb3TaYLaqhDkWQeV5N5XxYbn3+TvVJQyR+OSBfGoEpVP/
IS6fusvpT3eULJDax10By+gDcoLT5M1FNs4rBIvrAoGBAM8yJP5DHDwLjzl9vjsy
0iLaR3e8zBQTQV2nATvFAXKd3u0vW74rsX0XEdHgesFP8V0s3M4wlGj+wRL66j2y
5QJDfjMg9l7IJlHSX46CI5ks33X7xYy9evLYDs4R/Kct1q5OtsmGU8jisSadETus
jUb61kFvC7krovjVIgbuvWJ1AoGAVikzp4gVgeVU6AwePqu3JcpjYvX0SX4Br9VI
imq1oY49BAOa1PWYratoZp7kpjPiX2osRkaJStNEHExagtCjwaRuXpk0GIlT+p+S
yiGAsJUV4BrDh57B8IqbD6IKZgwnv2+ei0cIv562PdIxRXEDCd1rbZA3SqktA9KC
hgmXttkCgYBPU1lqRpwoHP9lpOBTDa6/Xi6WaDEWrG/tUF/wMlvrZ4hEVMDJRs1d
9JCXBxL/O4TMvpmyVKBZW15iZOcLM3EpiZ00UD+ChcAaFstup+oYKrs8gL9hgyTd
cvWMxGQm13KwSj2CLzEQpPAN5xG14njXaee5ksshxkzBz9z3MVWiiw==
-----END RSA PRIVATE KEY-----

@ -132,7 +132,7 @@ def get_avb_image_size(image):
return 0 return 0
def add_avb_footer(image, partition_size, extra_footer_args): def add_avb_footer(image, partition_size):
"""Appends a AVB hash footer to the image.""" """Appends a AVB hash footer to the image."""
avbtool_cmd = ['avbtool', 'add_hash_footer', '--image', image, avbtool_cmd = ['avbtool', 'add_hash_footer', '--image', image,
@ -143,7 +143,6 @@ def add_avb_footer(image, partition_size, extra_footer_args):
else: else:
avbtool_cmd.extend(['--dynamic_partition_size']) avbtool_cmd.extend(['--dynamic_partition_size'])
avbtool_cmd.extend(extra_footer_args)
subprocess.check_call(avbtool_cmd) subprocess.check_call(avbtool_cmd)
@ -161,24 +160,12 @@ def load_dict_from_file(path):
return d return d
def load_gki_info_file(gki_info_file, extra_args, extra_footer_args): def load_gki_info_file(gki_info_file, extra_args):
"""Loads extra arguments from the gki info file. """Loads extra args from |gki_info_file| into |extra_args|."""
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) info_dict = load_dict_from_file(gki_info_file)
if 'certify_bootimg_extra_args' in info_dict: if 'certify_bootimg_extra_args' in info_dict:
extra_args.extend( extra_args.extend(
shlex.split(info_dict['certify_bootimg_extra_args'])) 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): def get_archive_name_and_format_for_shutil(path):
@ -219,8 +206,6 @@ def parse_cmdline():
# Optional args. # Optional args.
parser.add_argument('--extra_args', default=[], action='append', parser.add_argument('--extra_args', default=[], action='append',
help='extra arguments to be forwarded to avbtool') 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() args = parser.parse_args()
@ -233,21 +218,13 @@ def parse_cmdline():
extra_args.extend(shlex.split(a)) extra_args.extend(shlex.split(a))
args.extra_args = extra_args 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: if args.gki_info:
load_gki_info_file(args.gki_info, load_gki_info_file(args.gki_info, args.extra_args)
args.extra_args,
args.extra_footer_args)
return 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.""" """Certify a GKI boot image by generating and appending a boot_signature."""
with tempfile.TemporaryDirectory() as temp_dir: with tempfile.TemporaryDirectory() as temp_dir:
boot_tmp = os.path.join(temp_dir, 'boot.tmp') boot_tmp = os.path.join(temp_dir, 'boot.tmp')
@ -257,27 +234,26 @@ def certify_bootimg(boot_img, output_img, algorithm, key, extra_args,
add_certificate(boot_tmp, algorithm, key, extra_args) add_certificate(boot_tmp, algorithm, key, extra_args)
avb_partition_size = get_avb_image_size(boot_img) avb_partition_size = get_avb_image_size(boot_img)
add_avb_footer(boot_tmp, avb_partition_size, extra_footer_args) add_avb_footer(boot_tmp, avb_partition_size)
# We're done, copy the temp image to the final output. # We're done, copy the temp image to the final output.
shutil.copy2(boot_tmp, output_img) shutil.copy2(boot_tmp, output_img)
def certify_bootimg_archive(boot_img_archive, output_archive, def certify_bootimg_archive(boot_img_archive, output_archive,
algorithm, key, extra_args, extra_footer_args): algorithm, key, extra_args):
"""Similar to certify_bootimg(), but for an archive of boot images.""" """Similar to certify_bootimg(), but for an archive of boot images."""
with tempfile.TemporaryDirectory() as unpack_dir: with tempfile.TemporaryDirectory() as unpack_dir:
shutil.unpack_archive(boot_img_archive, unpack_dir) shutil.unpack_archive(boot_img_archive, unpack_dir)
gki_info_file = os.path.join(unpack_dir, 'gki-info.txt') gki_info_file = os.path.join(unpack_dir, 'gki-info.txt')
if os.path.exists(gki_info_file): if os.path.exists(gki_info_file):
load_gki_info_file(gki_info_file, extra_args, extra_footer_args) load_gki_info_file(gki_info_file, extra_args)
for boot_img in glob.glob(os.path.join(unpack_dir, 'boot-*.img')): for boot_img in glob.glob(os.path.join(unpack_dir, 'boot*.img')):
print(f'Certifying {os.path.basename(boot_img)} ...') print(f'Certifying {os.path.basename(boot_img)} ...')
certify_bootimg(boot_img=boot_img, output_img=boot_img, certify_bootimg(boot_img=boot_img, output_img=boot_img,
algorithm=algorithm, key=key, extra_args=extra_args, algorithm=algorithm, key=key, extra_args=extra_args)
extra_footer_args=extra_footer_args)
print(f'Making certified archive: {output_archive}') print(f'Making certified archive: {output_archive}')
archive_file_name, archive_format = ( archive_file_name, archive_format = (
@ -299,11 +275,10 @@ def main():
if args.boot_img_archive: if args.boot_img_archive:
certify_bootimg_archive(args.boot_img_archive, args.output, certify_bootimg_archive(args.boot_img_archive, args.output,
args.algorithm, args.key, args.extra_args, args.algorithm, args.key, args.extra_args)
args.extra_footer_args)
else: else:
certify_bootimg(args.boot_img, args.output, args.algorithm, certify_bootimg(args.boot_img, args.output, args.algorithm,
args.key, args.extra_args, args.extra_footer_args) args.key, args.extra_args)
if __name__ == '__main__': if __name__ == '__main__':

@ -82,8 +82,8 @@ def generate_test_boot_image_archive(archive_file_name, archive_format,
e.g., 'zip', 'tar', or 'gztar', etc. e.g., 'zip', 'tar', or 'gztar', etc.
boot_img_info: a list of (boot_image_name, kernel_size, boot_img_info: a list of (boot_image_name, kernel_size,
partition_size) tuples. e.g., partition_size) tuples. e.g.,
[('boot-1.0.img', 4096, 4 * 1024), [('boot.img', 4096, 4 * 1024),
('boot-2.0.img', 8192, 8 * 1024)]. ('boot-lz4.img', 8192, 8 * 1024)].
gki_info: the file content to be written into 'gki-info.txt' in the gki_info: the file content to be written into 'gki-info.txt' in the
created archive. created archive.
@ -186,18 +186,18 @@ def extract_boot_signatures(boot_img, output_dir):
def extract_boot_archive_with_signatures(boot_img_archive, output_dir): def extract_boot_archive_with_signatures(boot_img_archive, output_dir):
"""Extracts boot images and signatures of a boot images archive. """Extracts boot images and signatures of a boot images archive.
Suppose there are two boot images in |boot_img_archive|: boot-1.0.img Suppose there are two boot images in |boot_img_archive|: boot.img
and boot-2.0.img. This function then extracts each boot-*.img and and boot-lz4.img. This function then extracts each boot*.img and
their signatures as: their signatures as:
- |output_dir|/boot-1.0.img - |output_dir|/boot.img
- |output_dir|/boot-2.0.img - |output_dir|/boot-lz4.img
- |output_dir|/boot-1.0/boot_signature1 - |output_dir|/boot/boot_signature1
- |output_dir|/boot-1.0/boot_signature2 - |output_dir|/boot/boot_signature2
- |output_dir|/boot-2.0/boot_signature1 - |output_dir|/boot-lz4/boot_signature1
- |output_dir|/boot-2.0/boot_signature2 - |output_dir|/boot-lz4/boot_signature2
""" """
shutil.unpack_archive(boot_img_archive, output_dir) shutil.unpack_archive(boot_img_archive, output_dir)
for boot_img in glob.glob(os.path.join(output_dir, 'boot-*.img')): for boot_img in glob.glob(os.path.join(output_dir, 'boot*.img')):
img_name = os.path.splitext(os.path.basename(boot_img))[0] img_name = os.path.splitext(os.path.basename(boot_img))[0]
signature_output_dir = os.path.join(output_dir, img_name) signature_output_dir = os.path.join(output_dir, img_name)
os.mkdir(signature_output_dir, 0o777) os.mkdir(signature_output_dir, 0o777)
@ -219,197 +219,6 @@ class CertifyBootimgTest(unittest.TestCase):
# C0103: invalid-name for maxDiff. # C0103: invalid-name for maxDiff.
self.maxDiff = None # pylint: disable=C0103 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 self._EXPECTED_BOOT_SIGNATURE_RSA2048 = ( # pylint: disable=C0103
'Minimum libavb version: 1.0\n' 'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n' 'Header Block: 256 bytes\n'
@ -576,7 +385,7 @@ class CertifyBootimgTest(unittest.TestCase):
" Prop: GKI_INFO -> 'added here'\n" " Prop: GKI_INFO -> 'added here'\n"
) )
self._EXPECTED_BOOT_1_0_SIGNATURE1_RSA4096 = ( # pylint: disable=C0103 self._EXPECTED_BOOT_SIGNATURE1_RSA4096 = ( # pylint: disable=C0103
'Minimum libavb version: 1.0\n' 'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n' 'Header Block: 256 bytes\n'
'Authentication Block: 576 bytes\n' 'Authentication Block: 576 bytes\n'
@ -595,8 +404,8 @@ class CertifyBootimgTest(unittest.TestCase):
' Partition Name: boot\n' # boot ' Partition Name: boot\n' # boot
' Salt: d00df00d\n' ' Salt: d00df00d\n'
' Digest: ' ' Digest: '
'88465e463bffb9f7dfc0c1f46d01bcf3' '30208b4d0a6d16db47fc13c9527bfe81'
'15f7693e19bd188a0ca1feca2ed7b9df\n' 'a168d3b3940325d1ca8d3439792bfe18\n'
' Flags: 0\n' ' Flags: 0\n'
" Prop: gki -> 'nice'\n" " Prop: gki -> 'nice'\n"
" Prop: space -> 'nice to meet you'\n" " Prop: space -> 'nice to meet you'\n"
@ -607,7 +416,7 @@ class CertifyBootimgTest(unittest.TestCase):
" Prop: SPACE -> 'nice to meet you'\n" " Prop: SPACE -> 'nice to meet you'\n"
) )
self._EXPECTED_BOOT_1_0_SIGNATURE2_RSA4096 = ( # pylint: disable=C0103 self._EXPECTED_BOOT_SIGNATURE2_RSA4096 = ( # pylint: disable=C0103
'Minimum libavb version: 1.0\n' 'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n' 'Header Block: 256 bytes\n'
'Authentication Block: 576 bytes\n' 'Authentication Block: 576 bytes\n'
@ -626,8 +435,8 @@ class CertifyBootimgTest(unittest.TestCase):
' Partition Name: generic_kernel\n' # generic_kernel ' Partition Name: generic_kernel\n' # generic_kernel
' Salt: d00df00d\n' ' Salt: d00df00d\n'
' Digest: ' ' Digest: '
'14ac8d0d233e57a317acd05cd458f2bb' 'd4c8847e7d9900a98f77e1f0b5272854'
'cc78725ef9f66c1b38e90697fb09d943\n' '7bf9c1e428fea500d419275f72ec5bd6\n'
' Flags: 0\n' ' Flags: 0\n'
" Prop: gki -> 'nice'\n" " Prop: gki -> 'nice'\n"
" Prop: space -> 'nice to meet you'\n" " Prop: space -> 'nice to meet you'\n"
@ -638,7 +447,7 @@ class CertifyBootimgTest(unittest.TestCase):
" Prop: SPACE -> 'nice to meet you'\n" " Prop: SPACE -> 'nice to meet you'\n"
) )
self._EXPECTED_BOOT_2_0_SIGNATURE1_RSA4096 = ( # pylint: disable=C0103 self._EXPECTED_BOOT_LZ4_SIGNATURE1_RSA4096 = ( # pylint: disable=C0103
'Minimum libavb version: 1.0\n' 'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n' 'Header Block: 256 bytes\n'
'Authentication Block: 576 bytes\n' 'Authentication Block: 576 bytes\n'
@ -657,8 +466,8 @@ class CertifyBootimgTest(unittest.TestCase):
' Partition Name: boot\n' # boot ' Partition Name: boot\n' # boot
' Salt: d00df00d\n' ' Salt: d00df00d\n'
' Digest: ' ' Digest: '
'3e6a9854a9d2350a7071083bc3f37376' '9d3a0670a9fd3de66e940117ef97700f'
'37573fd87b1c72b146cb4870ac6af36f\n' 'ed5fd1c6fb90798fd3873af45fc91cb4\n'
' Flags: 0\n' ' Flags: 0\n'
" Prop: gki -> 'nice'\n" " Prop: gki -> 'nice'\n"
" Prop: space -> 'nice to meet you'\n" " Prop: space -> 'nice to meet you'\n"
@ -669,7 +478,7 @@ class CertifyBootimgTest(unittest.TestCase):
" Prop: SPACE -> 'nice to meet you'\n" " Prop: SPACE -> 'nice to meet you'\n"
) )
self._EXPECTED_BOOT_2_0_SIGNATURE2_RSA4096 = ( # pylint: disable=C0103 self._EXPECTED_BOOT_LZ4_SIGNATURE2_RSA4096 = ( # pylint: disable=C0103
'Minimum libavb version: 1.0\n' 'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n' 'Header Block: 256 bytes\n'
'Authentication Block: 576 bytes\n' 'Authentication Block: 576 bytes\n'
@ -688,8 +497,8 @@ class CertifyBootimgTest(unittest.TestCase):
' Partition Name: generic_kernel\n' # generic_kernel ' Partition Name: generic_kernel\n' # generic_kernel
' Salt: d00df00d\n' ' Salt: d00df00d\n'
' Digest: ' ' Digest: '
'92fb8443cd284b67a4cbf5ce00348b50' '7d109e3dccca9e30e04249162d07e58c'
'1c657e0aedf4e2181c92ad7fc8b5224f\n' '62fdf269804b35857b956fba339b2679\n'
' Flags: 0\n' ' Flags: 0\n'
" Prop: gki -> 'nice'\n" " Prop: gki -> 'nice'\n"
" Prop: space -> 'nice to meet you'\n" " Prop: space -> 'nice to meet you'\n"
@ -700,7 +509,7 @@ class CertifyBootimgTest(unittest.TestCase):
" Prop: SPACE -> 'nice to meet you'\n" " Prop: SPACE -> 'nice to meet you'\n"
) )
self._EXPECTED_BOOT_3_0_SIGNATURE1_RSA4096 = ( # pylint: disable=C0103 self._EXPECTED_BOOT_GZ_SIGNATURE1_RSA4096 = ( # pylint: disable=C0103
'Minimum libavb version: 1.0\n' 'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n' 'Header Block: 256 bytes\n'
'Authentication Block: 576 bytes\n' 'Authentication Block: 576 bytes\n'
@ -719,14 +528,14 @@ class CertifyBootimgTest(unittest.TestCase):
' Partition Name: boot\n' # boot ' Partition Name: boot\n' # boot
' Salt: d00df00d\n' ' Salt: d00df00d\n'
' Digest: ' ' Digest: '
'9b9cd845a367d7fc9b61d6ac02b0e7c9' '6fcddc6167ae3c2037b424d35c3ef107'
'dc3d3b219abf60dd6e19359f0353c917\n' 'f586510dbb2d652d7c08b88e6ea52fc6\n'
' Flags: 0\n' ' Flags: 0\n'
" Prop: gki -> 'nice'\n" " Prop: gki -> 'nice'\n"
" Prop: space -> 'nice to meet you'\n" " Prop: space -> 'nice to meet you'\n"
) )
self._EXPECTED_BOOT_3_0_SIGNATURE2_RSA4096 = ( # pylint: disable=C0103 self._EXPECTED_BOOT_GZ_SIGNATURE2_RSA4096 = ( # pylint: disable=C0103
'Minimum libavb version: 1.0\n' 'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n' 'Header Block: 256 bytes\n'
'Authentication Block: 576 bytes\n' 'Authentication Block: 576 bytes\n'
@ -745,8 +554,8 @@ class CertifyBootimgTest(unittest.TestCase):
' Partition Name: generic_kernel\n' # generic_kernel ' Partition Name: generic_kernel\n' # generic_kernel
' Salt: d00df00d\n' ' Salt: d00df00d\n'
' Digest: ' ' Digest: '
'0cd7d331ed9b32dcd92f00e2cac75595' '7a6a43eb4048b783346fb6d039103647'
'52199170afe788a8fcf1954f9ea072d0\n' '6c4313146da521467af282dff1838d0e\n'
' Flags: 0\n' ' Flags: 0\n'
" Prop: gki -> 'nice'\n" " Prop: gki -> 'nice'\n"
" Prop: space -> 'nice to meet you'\n" " Prop: space -> 'nice to meet you'\n"
@ -838,8 +647,6 @@ class CertifyBootimgTest(unittest.TestCase):
'--key', './testdata/testkey_rsa2048.pem', '--key', './testdata/testkey_rsa2048.pem',
'--extra_args', '--prop gki:nice ' '--extra_args', '--prop gki:nice '
'--prop space:"nice to meet you"', '--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, '--output', boot_certified_img,
] ]
subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir) subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir)
@ -848,13 +655,7 @@ class CertifyBootimgTest(unittest.TestCase):
self.assertTrue(has_avb_footer(boot_certified_img)) self.assertTrue(has_avb_footer(boot_certified_img))
self.assertEqual(os.path.getsize(boot_img), self.assertEqual(os.path.getsize(boot_img),
os.path.getsize(boot_certified_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) extract_boot_signatures(boot_certified_img, temp_out_dir)
self._test_boot_signatures( self._test_boot_signatures(
temp_out_dir, temp_out_dir,
@ -871,8 +672,6 @@ class CertifyBootimgTest(unittest.TestCase):
'--key', './testdata/testkey_rsa4096.pem', '--key', './testdata/testkey_rsa4096.pem',
'--extra_args', '--prop gki:nice ' '--extra_args', '--prop gki:nice '
'--prop space:"nice to meet you"', '--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, '--output', boot_certified2_img,
] ]
subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir) subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir)
@ -881,13 +680,7 @@ class CertifyBootimgTest(unittest.TestCase):
self.assertTrue(has_avb_footer(boot_certified2_img)) self.assertTrue(has_avb_footer(boot_certified2_img))
self.assertEqual(os.path.getsize(boot_certified_img), self.assertEqual(os.path.getsize(boot_certified_img),
os.path.getsize(boot_certified2_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) extract_boot_signatures(boot_certified2_img, temp_out_dir)
self._test_boot_signatures( self._test_boot_signatures(
temp_out_dir, temp_out_dir,
@ -907,11 +700,7 @@ class CertifyBootimgTest(unittest.TestCase):
'-android13-0-00544-ged21d463f856 ' '-android13-0-00544-ged21d463f856 '
'--prop BRANCH:android13-5.10-2022-05 ' '--prop BRANCH:android13-5.10-2022-05 '
'--prop BUILD_NUMBER:ab8295296 ' '--prop BUILD_NUMBER:ab8295296 '
'--prop GKI_INFO:"added here"\n' '--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') gki_info_path = os.path.join(temp_out_dir, 'gki-info.txt')
with open(gki_info_path, 'w', encoding='utf-8') as f: with open(gki_info_path, 'w', encoding='utf-8') as f:
f.write(gki_info) f.write(gki_info)
@ -926,8 +715,6 @@ class CertifyBootimgTest(unittest.TestCase):
'--key', './testdata/testkey_rsa4096.pem', '--key', './testdata/testkey_rsa4096.pem',
'--extra_args', '--prop gki:nice ' '--extra_args', '--prop gki:nice '
'--prop space:"nice to meet you"', '--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, '--gki_info', gki_info_path,
'--output', boot_certified_img, '--output', boot_certified_img,
] ]
@ -938,12 +725,6 @@ class CertifyBootimgTest(unittest.TestCase):
self.assertEqual(os.path.getsize(boot_img), self.assertEqual(os.path.getsize(boot_img),
os.path.getsize(boot_certified_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) extract_boot_signatures(boot_certified_img, temp_out_dir)
self._test_boot_signatures( self._test_boot_signatures(
temp_out_dir, temp_out_dir,
@ -990,17 +771,13 @@ class CertifyBootimgTest(unittest.TestCase):
'-android13-0-00544-ged21d463f856 ' '-android13-0-00544-ged21d463f856 '
'--prop BRANCH:android13-5.10-2022-05 ' '--prop BRANCH:android13-5.10-2022-05 '
'--prop BUILD_NUMBER:ab8295296 ' '--prop BUILD_NUMBER:ab8295296 '
'--prop SPACE:"nice to meet you"\n' '--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_path = generate_test_boot_image_archive(
boot_img_archive_name, boot_img_archive_name,
'gztar', 'gztar',
# A list of (boot_img_name, kernel_size, partition_size). # A list of (boot_img_name, kernel_size, partition_size).
[('boot-1.0.img', 8 * 1024, 128 * 1024), [('boot.img', 8 * 1024, 128 * 1024),
('boot-2.0.img', 16 * 1024, 256 * 1024)], ('boot-lz4.img', 16 * 1024, 256 * 1024)],
gki_info) gki_info)
# Certify the boot image archive, with a RSA4096 key. # Certify the boot image archive, with a RSA4096 key.
@ -1013,8 +790,6 @@ class CertifyBootimgTest(unittest.TestCase):
'--key', './testdata/testkey_rsa4096.pem', '--key', './testdata/testkey_rsa4096.pem',
'--extra_args', '--prop gki:nice ' '--extra_args', '--prop gki:nice '
'--prop space:"nice to meet you"', '--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, '--output', boot_certified_img_archive,
] ]
subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir) subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir)
@ -1023,31 +798,24 @@ class CertifyBootimgTest(unittest.TestCase):
temp_out_dir) temp_out_dir)
# Checks an AVB footer exists and the image size remains. # Checks an AVB footer exists and the image size remains.
boot_1_img = os.path.join(temp_out_dir, 'boot-1.0.img') boot_img = os.path.join(temp_out_dir, 'boot.img')
self.assertTrue(has_avb_footer(boot_1_img)) self.assertTrue(has_avb_footer(boot_img))
self.assertEqual(os.path.getsize(boot_1_img), 128 * 1024) self.assertEqual(os.path.getsize(boot_img), 128 * 1024)
boot_2_img = os.path.join(temp_out_dir, 'boot-2.0.img') boot_lz4_img = os.path.join(temp_out_dir, 'boot-lz4.img')
self.assertTrue(has_avb_footer(boot_2_img)) self.assertTrue(has_avb_footer(boot_lz4_img))
self.assertEqual(os.path.getsize(boot_2_img), 256 * 1024) self.assertEqual(os.path.getsize(boot_lz4_img), 256 * 1024)
# Checks the content in the AVB footer.
self._test_boot_signatures( self._test_boot_signatures(
temp_out_dir, temp_out_dir,
{'boot-1.0.img': self._EXPECTED_AVB_FOOTER_BOOT_1_0, {'boot/boot_signature1':
'boot-2.0.img': self._EXPECTED_AVB_FOOTER_BOOT_2_0}) self._EXPECTED_BOOT_SIGNATURE1_RSA4096,
'boot/boot_signature2':
# Checks the content in the GKI certificate. self._EXPECTED_BOOT_SIGNATURE2_RSA4096,
self._test_boot_signatures( 'boot-lz4/boot_signature1':
temp_out_dir, self._EXPECTED_BOOT_LZ4_SIGNATURE1_RSA4096,
{'boot-1.0/boot_signature1': 'boot-lz4/boot_signature2':
self._EXPECTED_BOOT_1_0_SIGNATURE1_RSA4096, self._EXPECTED_BOOT_LZ4_SIGNATURE2_RSA4096})
'boot-1.0/boot_signature2':
self._EXPECTED_BOOT_1_0_SIGNATURE2_RSA4096,
'boot-2.0/boot_signature1':
self._EXPECTED_BOOT_2_0_SIGNATURE1_RSA4096,
'boot-2.0/boot_signature2':
self._EXPECTED_BOOT_2_0_SIGNATURE2_RSA4096})
def test_certify_bootimg_archive_without_gki_info(self): def test_certify_bootimg_archive_without_gki_info(self):
"""Tests certify_bootimg for a boot images archive.""" """Tests certify_bootimg for a boot images archive."""
@ -1060,7 +828,7 @@ class CertifyBootimgTest(unittest.TestCase):
boot_img_archive_name, boot_img_archive_name,
'zip', 'zip',
# A list of (boot_img_name, kernel_size, partition_size). # A list of (boot_img_name, kernel_size, partition_size).
[('boot-3.0.img', 8 * 1024, 128 * 1024)], [('boot-gz.img', 8 * 1024, 128 * 1024)],
gki_info=None) gki_info=None)
# Certify the boot image archive, with a RSA4096 key. # Certify the boot image archive, with a RSA4096 key.
boot_certified_img_archive = os.path.join( boot_certified_img_archive = os.path.join(
@ -1072,8 +840,6 @@ class CertifyBootimgTest(unittest.TestCase):
'--key', './testdata/testkey_rsa4096.pem', '--key', './testdata/testkey_rsa4096.pem',
'--extra_args', '--prop gki:nice ' '--extra_args', '--prop gki:nice '
'--prop space:"nice to meet you"', '--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, '--output', boot_certified_img_archive,
] ]
subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir) subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir)
@ -1084,7 +850,7 @@ class CertifyBootimgTest(unittest.TestCase):
boot_img_archive_name, boot_img_archive_name,
'tar', 'tar',
# A list of (boot_img_name, kernel_size, partition_size). # A list of (boot_img_name, kernel_size, partition_size).
[('boot-3.0.img', 8 * 1024, 128 * 1024)], [('boot-gz.img', 8 * 1024, 128 * 1024)],
gki_info='a=b\n' gki_info='a=b\n'
'c=d\n') 'c=d\n')
# Certify the boot image archive, with a RSA4096 key. # Certify the boot image archive, with a RSA4096 key.
@ -1097,8 +863,6 @@ class CertifyBootimgTest(unittest.TestCase):
'--key', './testdata/testkey_rsa4096.pem', '--key', './testdata/testkey_rsa4096.pem',
'--extra_args', '--prop gki:nice ' '--extra_args', '--prop gki:nice '
'--prop space:"nice to meet you"', '--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, '--output', boot_certified_img_archive2,
] ]
subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir) subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir)
@ -1107,22 +871,16 @@ class CertifyBootimgTest(unittest.TestCase):
temp_out_dir) temp_out_dir)
# Checks an AVB footer exists and the image size remains. # Checks an AVB footer exists and the image size remains.
boot_3_img = os.path.join(temp_out_dir, 'boot-3.0.img') boot_3_img = os.path.join(temp_out_dir, 'boot-gz.img')
self.assertTrue(has_avb_footer(boot_3_img)) self.assertTrue(has_avb_footer(boot_3_img))
self.assertEqual(os.path.getsize(boot_3_img), 128 * 1024) 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( self._test_boot_signatures(
temp_out_dir, temp_out_dir,
{'boot-3.0/boot_signature1': {'boot-gz/boot_signature1':
self._EXPECTED_BOOT_3_0_SIGNATURE1_RSA4096, self._EXPECTED_BOOT_GZ_SIGNATURE1_RSA4096,
'boot-3.0/boot_signature2': 'boot-gz/boot_signature2':
self._EXPECTED_BOOT_3_0_SIGNATURE2_RSA4096}) self._EXPECTED_BOOT_GZ_SIGNATURE2_RSA4096})
# I don't know how, but we need both the logger configuration and verbosity # I don't know how, but we need both the logger configuration and verbosity

@ -44,6 +44,7 @@ def generate_gki_certificate(image, avbtool, name, algorithm, key, salt,
avbtool_cmd += ['--salt', salt] avbtool_cmd += ['--salt', salt]
avbtool_cmd += additional_avb_args avbtool_cmd += additional_avb_args
print(avbtool_cmd)
subprocess.check_call(avbtool_cmd) subprocess.check_call(avbtool_cmd)

@ -19,19 +19,19 @@ import avb.blob.AuthBlob
import avb.blob.AuxBlob import avb.blob.AuxBlob
import avb.blob.Footer import avb.blob.Footer
import avb.blob.Header import avb.blob.Header
import avb.desc.* import avb.desc.UnknownDescriptor
import cfig.Avb import cfig.Avb
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.Helper.Companion.paddingWith import cfig.helper.Helper.Companion.paddingWith
import cfig.helper.Helper.DataSrc
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.commons.codec.binary.Hex import org.apache.commons.codec.binary.Hex
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.File import java.io.File
import java.io.FileInputStream
/* /*
a wonderfaul base64 encoder/decoder: https://cryptii.com/base64-to-hex a wonderful base64 encoder/decoder: https://cryptii.com/base64-to-hex
*/ */
class AVBInfo( class AVBInfo(
var header: Header? = null, var header: Header? = null,
@ -64,7 +64,7 @@ class AVBInfo(
public_key_metadata_size = auxBlob!!.pubkeyMeta?.pkmd?.size?.toLong() ?: 0L public_key_metadata_size = auxBlob!!.pubkeyMeta?.pkmd?.size?.toLong() ?: 0L
public_key_metadata_offset = public_key_offset + public_key_size public_key_metadata_offset = public_key_offset + public_key_size
log.info("pkmd size: $public_key_metadata_size, pkmd offset : $public_key_metadata_offset") log.debug("pkmd size: $public_key_metadata_size, pkmd offset : $public_key_metadata_offset")
}.encode() }.encode()
//2 - auth blob //2 - auth blob
val authBlob = AuthBlob.createBlob(headerBlob, newAuxBlob, alg.name) val authBlob = AuthBlob.createBlob(headerBlob, newAuxBlob, alg.name)
@ -88,33 +88,29 @@ class AVBInfo(
private val log = LoggerFactory.getLogger(AVBInfo::class.java) private val log = LoggerFactory.getLogger(AVBInfo::class.java)
private val mapper = ObjectMapper() private val mapper = ObjectMapper()
private data class Glance( data class Glance(
var footer: Footer?, var footer: Footer?,
var vbMetaOffset: Long var vbMetaOffset: Long
) )
private fun imageGlance(imageFile: String): Glance { private fun imageGlance(dataSrc: DataSrc<*>): Glance {
val ret = Glance(null, 0) val ret = Glance(null, 0)
// footer
FileInputStream(imageFile).use { fis ->
fis.skip(File(imageFile).length() - Footer.SIZE)
try { try {
ret.footer = Footer(fis) ret.footer = Footer(dataSrc.readFully(Pair(-Footer.SIZE.toLong(), Footer.SIZE)))
ret.vbMetaOffset = ret.footer!!.vbMetaOffset ret.vbMetaOffset = ret.footer!!.vbMetaOffset
log.info("$imageFile: $ret.footer") log.info("${dataSrc.getName()}: $ret.footer")
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
log.info("image $imageFile has no AVB Footer") log.info("image ${dataSrc.getName()} has no AVB Footer")
}
} }
return ret return ret
} }
fun parseFrom(imageFile: String): AVBInfo { fun parseFrom(dataSrc: DataSrc<*>): AVBInfo {
log.info("parseFrom($imageFile) ...") log.info("parseFrom(${dataSrc.getName()}) ...")
// glance // glance
val (footer, vbMetaOffset) = imageGlance(imageFile) val (footer, vbMetaOffset) = imageGlance(dataSrc)
// header // header
val vbMetaHeader = Header(ByteArrayInputStream(Helper.readFully(imageFile, vbMetaOffset, Header.SIZE))) val vbMetaHeader = Header(dataSrc.readFully(Pair(vbMetaOffset, Header.SIZE)))
log.debug(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(vbMetaHeader)) log.debug(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(vbMetaHeader))
val atlas = mutableMapOf<String, Pair<Long, Int>>() val atlas = mutableMapOf<String, Pair<Long, Int>>()
@ -130,9 +126,9 @@ class AVBInfo(
val ai = AVBInfo(vbMetaHeader, null, AuxBlob(), footer) val ai = AVBInfo(vbMetaHeader, null, AuxBlob(), footer)
// Auth blob // Auth blob
if (vbMetaHeader.authentication_data_block_size > 0) { if (vbMetaHeader.authentication_data_block_size > 0) {
val ba = Helper.readFully(imageFile, atlas["auth.hash"]!!) val ba = dataSrc.readFully(atlas["auth.hash"]!!)
log.debug("Parsed Auth Hash (Header & Aux Blob): " + Hex.encodeHexString(ba)) log.debug("Parsed Auth Hash (Header & Aux Blob): " + Hex.encodeHexString(ba))
val bb = Helper.readFully(imageFile, atlas["auth.sig"]!!) val bb = dataSrc.readFully(atlas["auth.sig"]!!)
log.debug("Parsed Auth Signature (of hash): " + Hex.encodeHexString(bb)) log.debug("Parsed Auth Signature (of hash): " + Hex.encodeHexString(bb))
ai.authBlob = AuthBlob() ai.authBlob = AuthBlob()
ai.authBlob!!.offset = atlas["auth"]!!.first ai.authBlob!!.offset = atlas["auth"]!!.first
@ -141,10 +137,10 @@ class AVBInfo(
ai.authBlob!!.signature = Hex.encodeHexString(bb) ai.authBlob!!.signature = Hex.encodeHexString(bb)
} }
// aux // aux
val rawAuxBlob = Helper.readFully(imageFile, atlas["aux"]!!) val rawAuxBlob = dataSrc.readFully(atlas["aux"]!!)
// aux - desc // aux - desc
if (vbMetaHeader.descriptors_size > 0) { if (vbMetaHeader.descriptors_size > 0) {
val descriptors = UnknownDescriptor.parseDescriptors2( val descriptors = UnknownDescriptor.parseDescriptors(
ByteArrayInputStream( ByteArrayInputStream(
rawAuxBlob.copyOfRange(vbMetaHeader.descriptors_offset.toInt(), rawAuxBlob.size) rawAuxBlob.copyOfRange(vbMetaHeader.descriptors_offset.toInt(), rawAuxBlob.size)
), ),
@ -176,7 +172,7 @@ class AVBInfo(
) )
log.debug("Parsed Pub Key Metadata: " + Helper.toHexString(ai.auxBlob!!.pubkeyMeta!!.pkmd)) log.debug("Parsed Pub Key Metadata: " + Helper.toHexString(ai.auxBlob!!.pubkeyMeta!!.pkmd))
} }
log.debug("vbmeta info of [$imageFile] has been analyzed") log.debug("vbmeta info of [${dataSrc.getName()}] has been analyzed")
return ai return ai
} }
} }

@ -20,10 +20,11 @@ import avb.blob.AuthBlob
import avb.blob.AuxBlob import avb.blob.AuxBlob
import avb.blob.Footer import avb.blob.Footer
import avb.blob.Header import avb.blob.Header
import avb.desc.* import avb.desc.HashDescriptor
import cfig.helper.CryptoHelper import cfig.helper.CryptoHelper
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.Helper.Companion.paddingWith import cfig.helper.Helper.Companion.paddingWith
import cfig.helper.Helper.DataSrc
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.commons.codec.binary.Hex import org.apache.commons.codec.binary.Hex
import org.apache.commons.exec.CommandLine import org.apache.commons.exec.CommandLine
@ -106,7 +107,7 @@ class Avb {
log.info("4/4 Appending AVB footer (${footerBlobWithPadding.size} bytes)...") log.info("4/4 Appending AVB footer (${footerBlobWithPadding.size} bytes)...")
fos.write(footerBlobWithPadding) fos.write(footerBlobWithPadding)
} }
assert(partition_size == File(image_file).length()) { "generated file size mismatch" } check(partition_size == File(image_file).length()) { "generated file size mismatch" }
log.info("addHashFooter($image_file) done.") log.info("addHashFooter($image_file) done.")
} }
@ -162,99 +163,6 @@ class Avb {
} }
} }
fun verify(ai: AVBInfo, image_file: String, parent: String = ""): Array<Any> {
val ret: Array<Any> = arrayOf(true, "")
val localParent = if (parent.isEmpty()) image_file else parent
//header
val rawHeaderBlob = ByteArray(Header.SIZE).apply {
FileInputStream(image_file).use { fis ->
ai.footer?.let {
fis.skip(it.vbMetaOffset)
}
fis.read(this)
}
}
// aux
val rawAuxBlob = ByteArray(ai.header!!.auxiliary_data_block_size.toInt()).apply {
FileInputStream(image_file).use { fis ->
val vbOffset = if (ai.footer == null) 0 else ai.footer!!.vbMetaOffset
fis.skip(vbOffset + Header.SIZE + ai.header!!.authentication_data_block_size)
fis.read(this)
}
}
//integrity check
val declaredAlg = Algorithms.get(ai.header!!.algorithm_type)
if (declaredAlg!!.public_key_num_bytes > 0) {
if (AuxBlob.encodePubKey(declaredAlg).contentEquals(ai.auxBlob!!.pubkey!!.pubkey)) {
log.info("VERIFY($localParent): signed with dev key: " + declaredAlg.defaultKey)
} else {
log.info("VERIFY($localParent): signed with release key")
}
val calcHash = Helper.join(declaredAlg.padding, AuthBlob.calcHash(rawHeaderBlob, rawAuxBlob, declaredAlg.name))
val readHash = Helper.join(declaredAlg.padding, Helper.fromHexString(ai.authBlob!!.hash!!))
if (calcHash.contentEquals(readHash)) {
log.info("VERIFY($localParent->AuthBlob): verify hash... PASS")
val readPubKey = CryptoHelper.KeyBox.decodeRSAkey(ai.auxBlob!!.pubkey!!.pubkey)
val hashFromSig = CryptoHelper.Signer.rawRsa(readPubKey, Helper.fromHexString(ai.authBlob!!.signature!!))
if (hashFromSig.contentEquals(readHash)) {
log.info("VERIFY($localParent->AuthBlob): verify signature... PASS")
} else {
ret[0] = false
ret[1] = ret[1] as String + " verify signature fail;"
log.warn("read=" + Helper.toHexString(readHash) + ", calc=" + Helper.toHexString(calcHash))
log.warn("VERIFY($localParent->AuthBlob): verify signature... FAIL")
}
} else {
ret[0] = false
ret[1] = ret[1] as String + " verify hash fail"
log.warn("read=" + ai.authBlob!!.hash!! + ", calc=" + Helper.toHexString(calcHash))
log.warn("VERIFY($localParent->AuthBlob): verify hash... FAIL")
}
} else {
log.warn("VERIFY($localParent->AuthBlob): algorithm=[${declaredAlg.name}], no signature, skip")
}
val morePath = System.getenv("more")
val morePrefix = if (!morePath.isNullOrBlank()) "$morePath/" else ""
ai.auxBlob!!.chainPartitionDescriptors.forEach {
val vRet = it.verify(listOf(morePrefix + it.partition_name + ".img", it.partition_name + ".img"),
image_file + "->Chain[${it.partition_name}]")
if (vRet[0] as Boolean) {
log.info("VERIFY($localParent->Chain[${it.partition_name}]): " + "PASS")
} else {
ret[0] = false
ret[1] = ret[1] as String + "; " + vRet[1] as String
log.info("VERIFY($localParent->Chain[${it.partition_name}]): " + vRet[1] as String + "... FAIL")
}
}
ai.auxBlob!!.hashDescriptors.forEach {
val vRet = it.verify(listOf(morePrefix + it.partition_name + ".img", it.partition_name + ".img"),
image_file + "->HashDescriptor[${it.partition_name}]")
if (vRet[0] as Boolean) {
log.info("VERIFY($localParent->HashDescriptor[${it.partition_name}]): ${it.hash_algorithm} " + "PASS")
} else {
ret[0] = false
ret[1] = ret[1] as String + "; " + vRet[1] as String
log.info("VERIFY($localParent->HashDescriptor[${it.partition_name}]): ${it.hash_algorithm} " + vRet[1] as String + "... FAIL")
}
}
ai.auxBlob!!.hashTreeDescriptors.forEach {
val vRet = it.verify(listOf(morePrefix + it.partition_name + ".img", it.partition_name + ".img"),
image_file + "->HashTreeDescriptor[${it.partition_name}]")
if (vRet[0] as Boolean) {
log.info("VERIFY($localParent->HashTreeDescriptor[${it.partition_name}]): ${it.hash_algorithm} " + "PASS")
} else {
ret[0] = false
ret[1] = ret[1] as String + "; " + vRet[1] as String
log.info("VERIFY($localParent->HashTreeDescriptor[${it.partition_name}]): ${it.hash_algorithm} " + vRet[1] as String + "... FAIL")
}
}
return ret
}
companion object { companion object {
private val log = LoggerFactory.getLogger(Avb::class.java) private val log = LoggerFactory.getLogger(Avb::class.java)
const val BLOCK_SIZE = 4096 const val BLOCK_SIZE = 4096
@ -297,8 +205,8 @@ class Avb {
it.auxBlob!!.hashDescriptors.get(0).partition_name it.auxBlob!!.hashDescriptors.get(0).partition_name
} }
//read hashDescriptor from image //read hashDescriptor from image
val newHashDesc = AVBInfo.parseFrom("$fileName.signed") val newHashDesc = AVBInfo.parseFrom(DataSrc("$fileName.signed"))
assert(newHashDesc.auxBlob!!.hashDescriptors.size == 1) check(newHashDesc.auxBlob!!.hashDescriptors.size == 1)
var seq = -1 //means not found var seq = -1 //means not found
//main vbmeta //main vbmeta
ObjectMapper().readValue(File(getJsonFileName("vbmeta.img")), AVBInfo::class.java).apply { ObjectMapper().readValue(File(getJsonFileName("vbmeta.img")), AVBInfo::class.java).apply {
@ -327,5 +235,104 @@ class Avb {
log.debug("no companion vbmeta.img") log.debug("no companion vbmeta.img")
} }
} }
fun verify(ai: AVBInfo, image_file: String, parent: String = ""): Array<Any> {
val ret: Array<Any> = arrayOf(true, "")
val localParent = parent.ifEmpty { image_file }
//header
val rawHeaderBlob = DataSrc(image_file).readFully(Pair(ai.footer?.vbMetaOffset ?: 0, Header.SIZE))
// aux
val vbOffset = ai.footer?.vbMetaOffset ?: 0
//@formatter:off
val rawAuxBlob = DataSrc(image_file).readFully(
Pair(vbOffset + Header.SIZE + ai.header!!.authentication_data_block_size,
ai.header!!.auxiliary_data_block_size.toInt()))
//@formatter:on
//integrity check
val declaredAlg = Algorithms.get(ai.header!!.algorithm_type)
if (declaredAlg!!.public_key_num_bytes > 0) {
val gkiPubKey = if (declaredAlg.algorithm_type == 1) AuxBlob.encodePubKey(
declaredAlg,
File("aosp/make/target/product/gsi/testkey_rsa2048.pem").readBytes()
) else null
if (AuxBlob.encodePubKey(declaredAlg).contentEquals(ai.auxBlob!!.pubkey!!.pubkey)) {
log.info("VERIFY($localParent): signed with dev key: " + declaredAlg.defaultKey)
} else if (gkiPubKey.contentEquals(ai.auxBlob!!.pubkey!!.pubkey)) {
log.info("VERIFY($localParent): signed with dev GKI key: " + declaredAlg.defaultKey)
} else {
log.info("VERIFY($localParent): signed with release key")
}
val calcHash =
Helper.join(declaredAlg.padding, AuthBlob.calcHash(rawHeaderBlob, rawAuxBlob, declaredAlg.name))
val readHash = Helper.join(declaredAlg.padding, Helper.fromHexString(ai.authBlob!!.hash!!))
if (calcHash.contentEquals(readHash)) {
log.info("VERIFY($localParent->AuthBlob): verify hash... PASS")
val readPubKey = CryptoHelper.KeyBox.decodeRSAkey(ai.auxBlob!!.pubkey!!.pubkey)
val hashFromSig =
CryptoHelper.Signer.rawRsa(readPubKey, Helper.fromHexString(ai.authBlob!!.signature!!))
if (hashFromSig.contentEquals(readHash)) {
log.info("VERIFY($localParent->AuthBlob): verify signature... PASS")
} else {
ret[0] = false
ret[1] = ret[1] as String + " verify signature fail;"
log.warn("read=" + Helper.toHexString(readHash) + ", calc=" + Helper.toHexString(calcHash))
log.warn("VERIFY($localParent->AuthBlob): verify signature... FAIL")
}
} else {
ret[0] = false
ret[1] = ret[1] as String + " verify hash fail"
log.warn("read=" + ai.authBlob!!.hash!! + ", calc=" + Helper.toHexString(calcHash))
log.warn("VERIFY($localParent->AuthBlob): verify hash... FAIL")
}
} else {
log.warn("VERIFY($localParent->AuthBlob): algorithm=[${declaredAlg.name}], no signature, skip")
}
val prefixes = setOf(System.getenv("more"), System.getProperty("more")).filterNotNull()
.map { Paths.get(it).toString() + "/" }.toMutableList().apply { add("") }
ai.auxBlob!!.chainPartitionDescriptors.forEach {
val vRet = it.verify(
prefixes.map { prefix -> "$prefix${it.partition_name}.img" },
image_file + "->Chain[${it.partition_name}]"
)
if (vRet[0] as Boolean) {
log.info("VERIFY($localParent->Chain[${it.partition_name}]): " + "PASS")
} else {
ret[0] = false
ret[1] = ret[1] as String + "; " + vRet[1] as String
log.info("VERIFY($localParent->Chain[${it.partition_name}]): " + vRet[1] as String + "... FAIL")
}
}
ai.auxBlob!!.hashDescriptors.forEach {
val vRet = it.verify(
prefixes.map { prefix -> "$prefix${it.partition_name}.img" },
image_file + "->HashDescriptor[${it.partition_name}]"
)
if (vRet[0] as Boolean) {
log.info("VERIFY($localParent->HashDescriptor[${it.partition_name}]): ${it.hash_algorithm} " + "PASS")
} else {
ret[0] = false
ret[1] = ret[1] as String + "; " + vRet[1] as String
log.info("VERIFY($localParent->HashDescriptor[${it.partition_name}]): ${it.hash_algorithm} " + vRet[1] as String + "... FAIL")
}
}
ai.auxBlob!!.hashTreeDescriptors.forEach {
val vRet = it.verify(
prefixes.map { prefix -> "$prefix${it.partition_name}.img" },
image_file + "->HashTreeDescriptor[${it.partition_name}]"
)
if (vRet[0] as Boolean) {
log.info("VERIFY($localParent->HashTreeDescriptor[${it.partition_name}]): ${it.hash_algorithm} " + "PASS")
} else {
ret[0] = false
ret[1] = ret[1] as String + "; " + vRet[1] as String
log.info("VERIFY($localParent->HashTreeDescriptor[${it.partition_name}]): ${it.hash_algorithm} " + vRet[1] as String + "... FAIL")
}
}
return ret
}
} }
} }

@ -16,14 +16,13 @@ package avb.blob
import avb.alg.Algorithm import avb.alg.Algorithm
import avb.desc.* import avb.desc.*
import cc.cfig.io.Struct
import cfig.helper.CryptoHelper import cfig.helper.CryptoHelper
import cfig.helper.Helper import cfig.helper.Helper
import cc.cfig.io.Struct
import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import org.bouncycastle.asn1.pkcs.RSAPrivateKey import org.bouncycastle.asn1.pkcs.RSAPrivateKey
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.nio.file.Files import java.io.File
import java.nio.file.Paths
@JsonIgnoreProperties("descriptorSize") @JsonIgnoreProperties("descriptorSize")
class AuxBlob( class AuxBlob(
@ -34,7 +33,8 @@ class AuxBlob(
var hashDescriptors: MutableList<HashDescriptor> = mutableListOf(), var hashDescriptors: MutableList<HashDescriptor> = mutableListOf(),
var kernelCmdlineDescriptors: MutableList<KernelCmdlineDescriptor> = mutableListOf(), var kernelCmdlineDescriptors: MutableList<KernelCmdlineDescriptor> = mutableListOf(),
var chainPartitionDescriptors: MutableList<ChainPartitionDescriptor> = mutableListOf(), var chainPartitionDescriptors: MutableList<ChainPartitionDescriptor> = mutableListOf(),
var unknownDescriptors: MutableList<UnknownDescriptor> = mutableListOf()) { var unknownDescriptors: MutableList<UnknownDescriptor> = mutableListOf()
) {
val descriptorSize: Int val descriptorSize: Int
get(): Int { get(): Int {
@ -55,7 +55,8 @@ class AuxBlob(
private fun encodeDescriptors(): ByteArray { private fun encodeDescriptors(): ByteArray {
return mutableListOf<Descriptor>().let { descList -> return mutableListOf<Descriptor>().let { descList ->
arrayOf(this.propertyDescriptors, //tag 0 arrayOf(
this.propertyDescriptors, //tag 0
this.hashTreeDescriptors, //tag 1 this.hashTreeDescriptors, //tag 1
this.hashDescriptors, //tag 2 this.hashDescriptors, //tag 2
this.kernelCmdlineDescriptors, //tag 3 this.kernelCmdlineDescriptors, //tag 3
@ -97,7 +98,8 @@ class AuxBlob(
val auxSize = Helper.round_to_multiple( val auxSize = Helper.round_to_multiple(
(encodedDesc.size + encodedKey.size + encodedPkmd.size).toLong(), (encodedDesc.size + encodedKey.size + encodedPkmd.size).toLong(),
64) 64
)
return Struct("${auxSize}b").pack(Helper.join(encodedDesc, encodedKey, encodedPkmd)) return Struct("${auxSize}b").pack(Helper.join(encodedDesc, encodedKey, encodedPkmd))
} }
@ -135,13 +137,10 @@ class AuxBlob(
fun encodePubKey(alg: Algorithm, key: ByteArray? = null): ByteArray { fun encodePubKey(alg: Algorithm, key: ByteArray? = null): ByteArray {
var encodedKey = byteArrayOf() var encodedKey = byteArrayOf()
if (alg.public_key_num_bytes > 0) { if (alg.public_key_num_bytes > 0) {
var algKey: ByteArray? = key val algKey: ByteArray = key ?: File(alg.defaultKey).readBytes()
if (key == null) { val rsa = CryptoHelper.KeyBox.parse4(algKey).key as RSAPrivateKey //BC RSA
algKey = Files.readAllBytes((Paths.get(alg.defaultKey)))
}
val rsa = CryptoHelper.KeyBox.parse4(algKey!!).key as RSAPrivateKey //BC RSA
encodedKey = CryptoHelper.KeyBox.encodeRSAkey(rsa) encodedKey = CryptoHelper.KeyBox.encodeRSAkey(rsa)
assert(alg.public_key_num_bytes == encodedKey.size) check(alg.public_key_num_bytes == encodedKey.size)
} else { } else {
log.info("encodePubKey(): No key to encode for algorithm " + alg.name) log.info("encodePubKey(): No key to encode for algorithm " + alg.name)
} }

@ -15,6 +15,7 @@
package avb.blob package avb.blob
import cc.cfig.io.Struct import cc.cfig.io.Struct
import java.io.ByteArrayInputStream
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.InputStream import java.io.InputStream
@ -47,7 +48,7 @@ data class Footer constructor(
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
constructor(iS: InputStream) : this() { constructor(iS: InputStream) : this() {
val info = Struct(FORMAT_STRING).unpack(iS) val info = Struct(FORMAT_STRING).unpack(iS)
assert(7 == info.size) check(7 == info.size)
if (MAGIC != (info[0] as String)) { if (MAGIC != (info[0] as String)) {
throw IllegalArgumentException("stream doesn't look like valid AVB Footer") throw IllegalArgumentException("stream doesn't look like valid AVB Footer")
} }
@ -58,6 +59,9 @@ data class Footer constructor(
vbMetaSize = (info[5] as ULong).toLong() vbMetaSize = (info[5] as ULong).toLong()
} }
@Throws(IllegalArgumentException::class)
constructor(data: ByteArray) : this(ByteArrayInputStream(data))
constructor(originalImageSize: Long, vbMetaOffset: Long, vbMetaSize: Long) constructor(originalImageSize: Long, vbMetaOffset: Long, vbMetaSize: Long)
: this(FOOTER_VERSION_MAJOR, FOOTER_VERSION_MINOR, originalImageSize, vbMetaOffset, vbMetaSize) : this(FOOTER_VERSION_MAJOR, FOOTER_VERSION_MINOR, originalImageSize, vbMetaOffset, vbMetaSize)
@ -93,7 +97,7 @@ data class Footer constructor(
private const val FORMAT_STRING = "!4s2L3Q${RESERVED}x" private const val FORMAT_STRING = "!4s2L3Q${RESERVED}x"
init { init {
assert(SIZE == Struct(FORMAT_STRING).calcSize()) check(SIZE == Struct(FORMAT_STRING).calcSize())
} }
} }
} }

@ -16,6 +16,7 @@ package avb.blob
import cfig.Avb import cfig.Avb
import cc.cfig.io.Struct import cc.cfig.io.Struct
import java.io.ByteArrayInputStream
import java.io.InputStream import java.io.InputStream
//avbtool::AvbVBMetaHeader //avbtool::AvbVBMetaHeader
@ -41,7 +42,7 @@ data class Header(
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
constructor(iS: InputStream) : this() { constructor(iS: InputStream) : this() {
val info = Struct(FORMAT_STRING).unpack(iS) val info = Struct(FORMAT_STRING).unpack(iS)
assert(22 == info.size) check(22 == info.size)
if (info[0] != magic) { if (info[0] != magic) {
throw IllegalArgumentException("stream doesn't look like valid VBMeta Header") throw IllegalArgumentException("stream doesn't look like valid VBMeta Header")
} }
@ -66,6 +67,9 @@ data class Header(
this.release_string = info[19] as String this.release_string = info[19] as String
} }
@Throws(IllegalArgumentException::class)
constructor(data: ByteArray) : this(ByteArrayInputStream(data))
fun encode(): ByteArray { fun encode(): ByteArray {
return Struct(FORMAT_STRING).pack( return Struct(FORMAT_STRING).pack(
magic, //4s magic, //4s
@ -121,7 +125,7 @@ data class Header(
private const val FORMAT_STRING = ("!4s2L2QL11QL${REVERSED0}x47sx" + "${REVERSED}x") private const val FORMAT_STRING = ("!4s2L2QL11QL${REVERSED0}x47sx" + "${REVERSED}x")
init { init {
assert(SIZE == Struct(FORMAT_STRING).calcSize()) check(SIZE == Struct(FORMAT_STRING).calcSize())
} }
} }
} }

@ -17,6 +17,7 @@ package avb.desc
import avb.AVBInfo import avb.AVBInfo
import cfig.Avb import cfig.Avb
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.Helper.DataSrc
import cc.cfig.io.Struct import cc.cfig.io.Struct
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
@ -85,10 +86,10 @@ class ChainPartitionDescriptor(
val ret: Array<Any> = arrayOf(false, "file not found") val ret: Array<Any> = arrayOf(false, "file not found")
for (item in image_files) { for (item in image_files) {
if (File(item).exists()) { if (File(item).exists()) {
val subAi = AVBInfo.parseFrom(item) val subAi = AVBInfo.parseFrom(DataSrc(item))
if (pubkey.contentEquals(subAi.auxBlob!!.pubkey!!.pubkey)) { if (pubkey.contentEquals(subAi.auxBlob!!.pubkey!!.pubkey)) {
log.info("VERIFY($parent): public key matches, PASS") log.info("VERIFY($parent): public key matches, PASS")
return Avb().verify(subAi, item, parent) return Avb.verify(subAi, item, parent)
} else { } else {
log.info("VERIFY($parent): public key mismatch, FAIL") log.info("VERIFY($parent): public key mismatch, FAIL")
ret[1] = "public key mismatch" ret[1] = "public key mismatch"

@ -61,7 +61,7 @@ class HashDescriptor(var flags: Int = 0,
throw IllegalArgumentException("Given data does not look like a |hash| descriptor") throw IllegalArgumentException("Given data does not look like a |hash| descriptor")
} }
val payload = Struct("${this.partition_name_len}s${this.salt_len}b${this.digest_len}b").unpack(data) val payload = Struct("${this.partition_name_len}s${this.salt_len}b${this.digest_len}b").unpack(data)
assert(3 == payload.size) check(3 == payload.size)
this.partition_name = payload[0] as String this.partition_name = payload[0] as String
this.salt = payload[1] as ByteArray this.salt = payload[1] as ByteArray
this.digest = payload[2] as ByteArray this.digest = payload[2] as ByteArray

@ -17,6 +17,7 @@ package avb.desc
import avb.blob.Header import avb.blob.Header
import cfig.helper.CryptoHelper import cfig.helper.CryptoHelper
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.Helper.DataSrc
import cc.cfig.io.Struct import cc.cfig.io.Struct
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.* import java.io.*
@ -112,11 +113,7 @@ class HashTreeDescriptor(
for (item in fileNames) { for (item in fileNames) {
if (File(item).exists()) { if (File(item).exists()) {
val trimmedHash = this.genMerkleTree(item, "hash.tree") val trimmedHash = this.genMerkleTree(item, "hash.tree")
val readTree = ByteArray(this.tree_size.toInt()) val readTree = DataSrc(item).readFully(Pair(this.tree_offset, this.tree_size.toInt()))
FileInputStream(item).use { fis ->
fis.skip(this.tree_offset)
fis.read(readTree)
}
val ourHtHash = CryptoHelper.Hasher.sha256(File("hash.tree").readBytes()) val ourHtHash = CryptoHelper.Hasher.sha256(File("hash.tree").readBytes())
val diskHtHash = CryptoHelper.Hasher.sha256(readTree) val diskHtHash = CryptoHelper.Hasher.sha256(readTree)
if (!ourHtHash.contentEquals(diskHtHash)) { if (!ourHtHash.contentEquals(diskHtHash)) {

@ -72,7 +72,7 @@ class KernelCmdlineDescriptor(
const val flagHashTreeDisabled = 2 const val flagHashTreeDisabled = 2
init { init {
assert(SIZE == Struct(FORMAT_STRING).calcSize()) check(SIZE == Struct(FORMAT_STRING).calcSize())
} }
} }
} }

@ -71,30 +71,7 @@ class UnknownDescriptor(var data: ByteArray = byteArrayOf()) : Descriptor(0, 0,
private const val FORMAT = "!QQ" private const val FORMAT = "!QQ"
private val log = LoggerFactory.getLogger(UnknownDescriptor::class.java) private val log = LoggerFactory.getLogger(UnknownDescriptor::class.java)
fun parseDescriptors(stream: InputStream, totalSize: Long): List<UnknownDescriptor> { fun parseDescriptors(stream: InputStream, totalSize: Long): List<Descriptor> {
log.debug("Parse descriptors stream, SIZE = $totalSize")
val ret: MutableList<UnknownDescriptor> = mutableListOf()
var currentSize = 0L
while (true) {
val desc = UnknownDescriptor(stream)
currentSize += desc.data.size + SIZE
log.debug("current SIZE = $currentSize")
ret.add(desc)
if (currentSize == totalSize) {
log.debug("parse descriptor done")
break
} else if (currentSize > totalSize) {
log.error("Read more than expected")
throw IllegalStateException("Read more than expected")
} else {
log.debug(desc.toString())
log.debug("read another descriptor")
}
}
return ret
}
fun parseDescriptors2(stream: InputStream, totalSize: Long): List<Descriptor> {
log.debug("Parse descriptors stream, SIZE = $totalSize") log.debug("Parse descriptors stream, SIZE = $totalSize")
val ret: MutableList<Descriptor> = mutableListOf() val ret: MutableList<Descriptor> = mutableListOf()
var currentSize = 0L var currentSize = 0L
@ -120,7 +97,7 @@ class UnknownDescriptor(var data: ByteArray = byteArrayOf()) : Descriptor(0, 0,
} }
init { init {
assert(SIZE == Struct(FORMAT).calcSize()) check(SIZE == Struct(FORMAT).calcSize())
} }
} }
} }

@ -59,7 +59,7 @@ open class BootHeaderV2(
} }
log.warn("BootImgHeader constructor") log.warn("BootImgHeader constructor")
val info = Struct(FORMAT_STRING).unpack(iS) val info = Struct(FORMAT_STRING).unpack(iS)
assert(20 == info.size) check(20 == info.size)
if (info[0] != magic) { if (info[0] != magic) {
throw IllegalArgumentException("stream doesn't look like Android Boot Image Header") throw IllegalArgumentException("stream doesn't look like Android Boot Image Header")
} }
@ -87,7 +87,7 @@ open class BootHeaderV2(
} }
this.headerSize = (info[17] as UInt).toInt() this.headerSize = (info[17] as UInt).toInt()
assert(this.headerSize.toInt() in intArrayOf(BOOT_IMAGE_HEADER_V2_SIZE, check(this.headerSize.toInt() in intArrayOf(BOOT_IMAGE_HEADER_V2_SIZE,
BOOT_IMAGE_HEADER_V1_SIZE, BOOT_IMAGE_HEADER_V0_SIZE)) { BOOT_IMAGE_HEADER_V1_SIZE, BOOT_IMAGE_HEADER_V0_SIZE)) {
"header size ${this.headerSize} illegal" "header size ${this.headerSize} illegal"
} }
@ -109,7 +109,7 @@ open class BootHeaderV2(
val pageSizeChoices: MutableSet<Long> = mutableSetOf<Long>().apply { val pageSizeChoices: MutableSet<Long> = mutableSetOf<Long>().apply {
(11..14).forEach { add(2.0.pow(it).toLong()) } (11..14).forEach { add(2.0.pow(it).toLong()) }
} }
assert(pageSizeChoices.contains(pageSize.toLong())) { "invalid parameter [pageSize=$pageSize], (choose from $pageSizeChoices)" } check(pageSizeChoices.contains(pageSize.toLong())) { "invalid parameter [pageSize=$pageSize], (choose from $pageSizeChoices)" }
return Struct(FORMAT_STRING).pack( return Struct(FORMAT_STRING).pack(
magic, magic,
//10I //10I
@ -168,8 +168,7 @@ open class BootHeaderV2(
const val BOOT_IMAGE_HEADER_V0_SIZE = 0 const val BOOT_IMAGE_HEADER_V0_SIZE = 0
init { init {
assert(BOOT_IMAGE_HEADER_V2_SIZE == Struct(FORMAT_STRING).calcSize()) check(BOOT_IMAGE_HEADER_V2_SIZE == Struct(FORMAT_STRING).calcSize())
} }
} }
} }

@ -21,6 +21,7 @@ import cfig.bootimg.Common.Companion.deleleIfExists
import cfig.bootimg.Signer import cfig.bootimg.Signer
import cfig.bootimg.v3.VendorBoot import cfig.bootimg.v3.VendorBoot
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.Helper.DataSrc
import cfig.packable.VBMetaParser import cfig.packable.VBMetaParser
import cfig.utils.EnvironmentVerifier import cfig.utils.EnvironmentVerifier
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
@ -225,7 +226,7 @@ data class BootV2(
fun extractVBMeta(): BootV2 { fun extractVBMeta(): BootV2 {
if (this.info.verify.startsWith("VB2.0")) { if (this.info.verify.startsWith("VB2.0")) {
AVBInfo.parseFrom(info.output).dumpDefault(info.output) AVBInfo.parseFrom(DataSrc(info.output)).dumpDefault(info.output)
if (File("vbmeta.img").exists()) { if (File("vbmeta.img").exists()) {
log.warn("Found vbmeta.img, parsing ...") log.warn("Found vbmeta.img, parsing ...")
VBMetaParser().unpack("vbmeta.img") VBMetaParser().unpack("vbmeta.img")

@ -21,6 +21,7 @@ import cfig.bootimg.Common.Companion.deleleIfExists
import cfig.bootimg.Signer import cfig.bootimg.Signer
import cfig.bootimg.v3.VendorBoot import cfig.bootimg.v3.VendorBoot
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.Helper.DataSrc
import cfig.packable.VBMetaParser import cfig.packable.VBMetaParser
import cfig.utils.EnvironmentVerifier import cfig.utils.EnvironmentVerifier
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
@ -244,7 +245,7 @@ data class BootV2Dialects(
fun extractVBMeta(): BootV2Dialects { fun extractVBMeta(): BootV2Dialects {
if (this.info.verify.startsWith("VB2.0")) { if (this.info.verify.startsWith("VB2.0")) {
AVBInfo.parseFrom(info.output).dumpDefault(info.output) AVBInfo.parseFrom(DataSrc(info.output)).dumpDefault(info.output)
if (File("vbmeta.img").exists()) { if (File("vbmeta.img").exists()) {
log.warn("Found vbmeta.img, parsing ...") log.warn("Found vbmeta.img, parsing ...")
VBMetaParser().unpack("vbmeta.img") VBMetaParser().unpack("vbmeta.img")

@ -36,7 +36,7 @@ class BootHeaderV3(
} }
log.warn("BootImgHeaderV3/V4 constructor") log.warn("BootImgHeaderV3/V4 constructor")
val info = Struct(FORMAT_STRING).unpack(iS) val info = Struct(FORMAT_STRING).unpack(iS)
assert(12 == info.size) check(12 == info.size)
if (info[0] != magic) { if (info[0] != magic) {
throw IllegalArgumentException("stream doesn't look like Android Boot Image V3 Header") throw IllegalArgumentException("stream doesn't look like Android Boot Image V3 Header")
} }
@ -52,7 +52,7 @@ class BootHeaderV3(
this.headerVersion = (info[9] as UInt).toInt() this.headerVersion = (info[9] as UInt).toInt()
this.cmdline = info[10] as String this.cmdline = info[10] as String
this.signatureSize = (info[11] as UInt).toInt() this.signatureSize = (info[11] as UInt).toInt()
assert(this.headerSize in intArrayOf(BOOT_IMAGE_HEADER_V3_SIZE, BOOT_IMAGE_HEADER_V4_SIZE)) check(this.headerSize in intArrayOf(BOOT_IMAGE_HEADER_V3_SIZE, BOOT_IMAGE_HEADER_V4_SIZE))
} }
fun encode(): ByteArray { fun encode(): ByteArray {
@ -106,7 +106,7 @@ class BootHeaderV3(
const val pageSize: Int = 4096 const val pageSize: Int = 4096
init { init {
assert(BOOT_IMAGE_HEADER_V4_SIZE == Struct(FORMAT_STRING).calcSize()) { check(BOOT_IMAGE_HEADER_V4_SIZE == Struct(FORMAT_STRING).calcSize()) {
"internal error: expected size $BOOT_IMAGE_HEADER_V4_SIZE " "internal error: expected size $BOOT_IMAGE_HEADER_V4_SIZE "
} }
} }

@ -23,6 +23,7 @@ import cfig.bootimg.Common.Companion.deleleIfExists
import cfig.bootimg.Common.Companion.getPaddingSize import cfig.bootimg.Common.Companion.getPaddingSize
import cfig.bootimg.Signer import cfig.bootimg.Signer
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.Helper.DataSrc
import cfig.packable.VBMetaParser import cfig.packable.VBMetaParser
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import de.vandermeer.asciitable.AsciiTable import de.vandermeer.asciitable.AsciiTable
@ -231,17 +232,6 @@ data class BootV3(
this.ramdisk.file = this.ramdisk.file + ".$fmt" this.ramdisk.file = this.ramdisk.file + ".$fmt"
} }
//bootsig //bootsig
if (info.signatureSize > 0) {
Helper.extractFile(
info.output, this.bootSignature.file,
this.bootSignature.position.toLong(), this.bootSignature.size
)
try {
AVBInfo.parseFrom(this.bootSignature.file).dumpDefault(this.bootSignature.file)
} catch (e: IllegalArgumentException) {
log.warn("boot signature is invalid")
}
}
//dump info again //dump info again
mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this) mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this)
@ -249,8 +239,9 @@ data class BootV3(
} }
fun extractVBMeta(): BootV3 { fun extractVBMeta(): BootV3 {
// vbmeta in image
try { try {
AVBInfo.parseFrom(info.output).dumpDefault(info.output) AVBInfo.parseFrom(DataSrc(info.output)).dumpDefault(info.output)
if (File("vbmeta.img").exists()) { if (File("vbmeta.img").exists()) {
log.warn("Found vbmeta.img, parsing ...") log.warn("Found vbmeta.img, parsing ...")
VBMetaParser().unpack("vbmeta.img") VBMetaParser().unpack("vbmeta.img")
@ -259,6 +250,51 @@ data class BootV3(
log.warn(e.message) log.warn(e.message)
log.warn("failed to parse vbmeta info") log.warn("failed to parse vbmeta info")
} }
//GKI 1.0 bootsig
if (info.signatureSize > 0) {
Helper.extractFile(
info.output, this.bootSignature.file,
this.bootSignature.position.toLong(), this.bootSignature.size
)
try {
val bootsig = AVBInfo.parseFrom(DataSrc(this.bootSignature.file)).dumpDefault(this.bootSignature.file)
Avb.verify(bootsig, "${workDir}bootsig")
} catch (e: IllegalArgumentException) {
log.warn("boot signature is invalid")
}
return this
}
//GKI 2.0 bootsig
if (!File(Avb.getJsonFileName(info.output)).exists()) {
log.info("no AVB info found in ${info.output}")
return this
}
log.info("probing 16KB boot signature ...")
val mainBlob = ObjectMapper().readValue(
File(Avb.getJsonFileName(info.output)),
AVBInfo::class.java
)
val bootSig16kData =
DataSrc(DataSrc(info.output).readFully(Pair(mainBlob.footer!!.originalImageSize - 16 * 1024, 16 * 1024)))
try {
val blob1 = AVBInfo.parseFrom(bootSig16kData)
.also { it.dumpDefault("bootsig." + it.auxBlob!!.hashDescriptors[0].partition_name) }
val blob2 =
AVBInfo.parseFrom(DataSrc(bootSig16kData.readFully(blob1.encode().size until bootSig16kData.getLength())))
.also { it.dumpDefault("bootsig." + it.auxBlob!!.hashDescriptors[0].partition_name) }
File("build/unzip_boot/generic_kernel_avb").writeBytes(bootSig16kData.readFully(blob1.encode().size until bootSig16kData.getLength()))
File("build/unzip_boot/kernel").copyTo(File("build/unzip_boot/generic_kernel.img"), true)
System.setProperty("more", "build/unzip_boot")
Avb.verify(blob2, "generic_kernel_avb")
log.info(blob1.auxBlob!!.hashDescriptors[0].partition_name)
log.info(blob2.auxBlob!!.hashDescriptors[0].partition_name)
} catch (e: IllegalArgumentException) {
log.warn("can not find boot signature: " + e.message)
}
return this return this
} }

@ -21,6 +21,7 @@ import cfig.utils.EnvironmentVerifier
import cfig.bootimg.Common.Companion.deleleIfExists import cfig.bootimg.Common.Companion.deleleIfExists
import cfig.bootimg.Signer import cfig.bootimg.Signer
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.Helper.DataSrc
import cfig.packable.VBMetaParser import cfig.packable.VBMetaParser
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import de.vandermeer.asciitable.AsciiTable import de.vandermeer.asciitable.AsciiTable
@ -124,7 +125,7 @@ data class VendorBoot(
const val SIZE = 108 const val SIZE = 108
init { init {
assert(Struct(FORMAT_STRING).calcSize() == SIZE) check(Struct(FORMAT_STRING).calcSize() == SIZE)
} }
} }
@ -133,7 +134,7 @@ data class VendorBoot(
return return
} }
val info = Struct(FORMAT_STRING).unpack(iS) val info = Struct(FORMAT_STRING).unpack(iS)
assert((3 + 1 + 1) == info.size) check((3 + 1 + 1) == info.size)
this.size = (info[0] as UInt).toInt() this.size = (info[0] as UInt).toInt()
this.offset = (info[1] as UInt).toInt() this.offset = (info[1] as UInt).toInt()
this.type = VrtType.fromInt((info[2] as UInt).toInt()) this.type = VrtType.fromInt((info[2] as UInt).toInt())
@ -372,7 +373,7 @@ data class VendorBoot(
fun extractVBMeta(): VendorBoot { fun extractVBMeta(): VendorBoot {
try { try {
AVBInfo.parseFrom(info.output).dumpDefault(info.output) AVBInfo.parseFrom(DataSrc(info.output)).dumpDefault(info.output)
} catch (e: Exception) { } catch (e: Exception) {
log.error("extraceVBMeta(): $e") log.error("extraceVBMeta(): $e")
} }

@ -42,7 +42,7 @@ class VendorBootHeader(
} }
log.warn("VendorBootHeader constructor") log.warn("VendorBootHeader constructor")
val info = Struct(FORMAT_STRING).unpack(iS) val info = Struct(FORMAT_STRING).unpack(iS)
assert(16 == info.size) check(16 == info.size)
if (info[0] != magic) { if (info[0] != magic) {
throw IllegalArgumentException("stream doesn't look like Android Vendor Boot Image") throw IllegalArgumentException("stream doesn't look like Android Vendor Boot Image")
} }
@ -134,7 +134,7 @@ class VendorBootHeader(
"I" //[v4] bootconfig size "I" //[v4] bootconfig size
init { init {
assert(Struct(FORMAT_STRING).calcSize() == VENDOR_BOOT_IMAGE_HEADER_V4_SIZE) check(Struct(FORMAT_STRING).calcSize() == VENDOR_BOOT_IMAGE_HEADER_V4_SIZE)
} }
} }

@ -89,7 +89,7 @@ data class MiscImage(
const val SIZE = 2048 const val SIZE = 2048
init { init {
assert(SIZE == Struct(FORMAT_STRING).calcSize()) check(SIZE == Struct(FORMAT_STRING).calcSize())
} }
/* /*
@ -188,7 +188,7 @@ data class MiscImage(
const val SIZE = 64 const val SIZE = 64
init { init {
assert(SIZE == Struct(FORMAT_STRING).calcSize()) check(SIZE == Struct(FORMAT_STRING).calcSize())
} }
} }

@ -19,6 +19,7 @@ import cfig.bootimg.Common.Companion.probeHeaderVersion
import cfig.bootimg.v2.BootV2 import cfig.bootimg.v2.BootV2
import cfig.bootimg.v2.BootV2Dialects import cfig.bootimg.v2.BootV2Dialects
import cfig.bootimg.v3.BootV3 import cfig.bootimg.v3.BootV3
import cfig.helper.Helper
import cfig.helper.Helper.Companion.deleteIfExists import cfig.helper.Helper.Companion.deleteIfExists
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import de.vandermeer.asciitable.AsciiTable import de.vandermeer.asciitable.AsciiTable
@ -119,6 +120,12 @@ class BootImgParser : IPackable {
} }
override fun `@verify`(fileName: String) { override fun `@verify`(fileName: String) {
File(Helper.prop("workDir")).let {
if (!it.exists()) {
it.mkdirs()
}
}
super.`@verify`(fileName) super.`@verify`(fileName)
} }

@ -17,6 +17,7 @@ package cfig.packable
import avb.AVBInfo import avb.AVBInfo
import cfig.Avb import cfig.Avb
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.Helper.DataSrc
import cfig.helper.Helper.Companion.check_call import cfig.helper.Helper.Companion.check_call
import cfig.helper.Helper.Companion.check_output import cfig.helper.Helper.Companion.check_output
import cfig.helper.Helper.Companion.deleteIfExists import cfig.helper.Helper.Companion.deleteIfExists
@ -67,8 +68,8 @@ interface IPackable {
// invoked solely by reflection // invoked solely by reflection
fun `@verify`(fileName: String) { fun `@verify`(fileName: String) {
val ai = AVBInfo.parseFrom(fileName).dumpDefault(fileName) val ai = AVBInfo.parseFrom(DataSrc(fileName)).dumpDefault(fileName)
Avb().verify(ai, fileName) Avb.verify(ai, fileName)
} }
fun clear() { fun clear() {

@ -17,6 +17,7 @@ package cfig.packable
import avb.AVBInfo import avb.AVBInfo
import cfig.Avb import cfig.Avb
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.Helper.DataSrc
import cfig.helper.Helper.Companion.deleteIfExists import cfig.helper.Helper.Companion.deleteIfExists
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -39,7 +40,7 @@ class VBMetaParser : IPackable {
it.mkdirs() it.mkdirs()
} }
} }
AVBInfo.parseFrom(fileName).dumpDefault(fileName) AVBInfo.parseFrom(DataSrc(fileName)).dumpDefault(fileName)
} }
override fun pack(fileName: String) { override fun pack(fileName: String) {

@ -7,6 +7,7 @@ import cfig.bootimg.Common
import cfig.bootimg.Signer import cfig.bootimg.Signer
import cfig.bootimg.v3.VendorBoot import cfig.bootimg.v3.VendorBoot
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.Helper.DataSrc
import cfig.packable.VBMetaParser import cfig.packable.VBMetaParser
import cfig.utils.DTC import cfig.utils.DTC
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
@ -44,7 +45,7 @@ class Dtbo(
internal const val SIZE = 32 internal const val SIZE = 32
init { init {
assert(Struct(FORMAT_STRING).calcSize() == SIZE) check(Struct(FORMAT_STRING).calcSize() == SIZE)
} }
} }
@ -53,7 +54,7 @@ class Dtbo(
return return
} }
val info = Struct(FORMAT_STRING).unpack(iS) val info = Struct(FORMAT_STRING).unpack(iS)
assert(8 == info.size) check(8 == info.size)
if ((info[0] as UInt).toLong() != magic) { if ((info[0] as UInt).toLong() != magic) {
throw IllegalArgumentException("stream doesn't look like DTBO header") throw IllegalArgumentException("stream doesn't look like DTBO header")
} }
@ -103,13 +104,13 @@ class Dtbo(
internal const val SIZE = 32 internal const val SIZE = 32
init { init {
assert(Struct(FORMAT_STRING).calcSize() == SIZE) check(Struct(FORMAT_STRING).calcSize() == SIZE)
} }
} }
constructor(iS: InputStream) : this() { constructor(iS: InputStream) : this() {
val info = Struct(FORMAT_STRING).unpack(iS) val info = Struct(FORMAT_STRING).unpack(iS)
assert(8 == info.size) check(8 == info.size)
entrySize = info[0] as Int entrySize = info[0] as Int
entryOffset = info[1] as Int entryOffset = info[1] as Int
id = info[2] as Int id = info[2] as Int
@ -155,7 +156,7 @@ class Dtbo(
fun extractVBMeta(): Dtbo { fun extractVBMeta(): Dtbo {
try { try {
AVBInfo.parseFrom(info.output).dumpDefault(info.output) AVBInfo.parseFrom(DataSrc(info.output)).dumpDefault(info.output)
} catch (e: Exception) { } catch (e: Exception) {
log.error("extraceVBMeta(): $e") log.error("extraceVBMeta(): $e")
} }

@ -6,7 +6,7 @@ verity_pk8 = aosp/security/verity.pk8
verity_pem = aosp/security/verity.x509.pem verity_pem = aosp/security/verity.x509.pem
kernelVersionFile = build/unzip_boot/kernel_version.txt kernelVersionFile = build/unzip_boot/kernel_version.txt
kernelConfigFile = build/unzip_boot/kernel_configs.txt kernelConfigFile = build/unzip_boot/kernel_configs.txt
kernelExtracter = aosp/build/tools/extract_kernel.py kernelExtracter = aosp/make/tools/extract_kernel.py
mkbootimg = aosp/system/tools/mkbootimg/mkbootimg.py mkbootimg = aosp/system/tools/mkbootimg/mkbootimg.py
dtboMaker = aosp/system/libufdt/utils/src/mkdtboimg.py dtboMaker = aosp/system/libufdt/utils/src/mkdtboimg.py
payloadDir = build/payload/ payloadDir = build/payload/

@ -42,7 +42,7 @@ class CryptoHelper {
val p = PemReader(InputStreamReader(ByteArrayInputStream(data))).readPemObject() val p = PemReader(InputStreamReader(ByteArrayInputStream(data))).readPemObject()
if (p != null) { if (p != null) {
log.info("parse PEM: " + p.type) log.debug("parse PEM: " + p.type)
ret = when (p.type) { ret = when (p.type) {
"RSA PUBLIC KEY", "PUBLIC KEY" -> { "RSA PUBLIC KEY", "PUBLIC KEY" -> {
try { try {
@ -174,13 +174,17 @@ class CryptoHelper {
require(rsa.modulus.bitLength() == numBits) require(rsa.modulus.bitLength() == numBits)
val b = BigInteger.valueOf(2).pow(32) val b = BigInteger.valueOf(2).pow(32)
val n0inv = b.minus(rsa.modulus.modInverse(b)).toLong() val n0inv = b.minus(rsa.modulus.modInverse(b)).toLong()
val rrModn = BigInteger.valueOf(4).pow(numBits).rem(rsa.modulus) val rrModn = BigInteger.valueOf(4).pow(numBits).rem(rsa.modulus).toByteArray().let {
val unsignedModulo = rsa.modulus.toByteArray().sliceArray(1..numBits / 8) //remove sign byte it.sliceArray(it.size - numBits/8 until it.size)
}
val unsignedModulo = rsa.modulus.toByteArray().let {
it.sliceArray(it.size - numBits/8 until it.size)
}
return Struct("!II${numBits / 8}b${numBits / 8}b").pack( return Struct("!II${numBits / 8}b${numBits / 8}b").pack(
numBits, numBits,
n0inv, n0inv,
unsignedModulo, unsignedModulo,
rrModn.toByteArray() rrModn
) )
} }

@ -38,6 +38,75 @@ class Helper {
var dumpFile: String var dumpFile: String
) )
class DataSrc<in T>(private val incoming: T) {
fun getName(): String {
return if (incoming is String) "FILE:$incoming" else "data"
}
fun getLength(): Long {
return when (incoming) {
is String -> {
File(incoming).length()
}
is ByteArray -> {
incoming.size.toLong()
}
else -> {
throw IllegalArgumentException("type ${incoming!!::class} is not supported")
}
}
}
@Throws(IllegalArgumentException::class)
fun readFully(range: LongRange): ByteArray {
when (incoming) {
is String -> {
return ByteArray(range.count()).apply {
FileInputStream(incoming).use { fis ->
fis.skip(range.first)
fis.read(this)
}
}
}
is ByteArray -> {
return incoming.sliceArray(range.first.toInt()..range.last.toInt())
}
else -> {
throw IllegalArgumentException("type ${incoming!!::class} is not supported")
}
}
}
@Throws(IllegalArgumentException::class)
fun readFully(loc: Pair<Long, Int>): ByteArray {
when (incoming) {
is String -> {
return ByteArray(loc.second).apply {
FileInputStream(incoming).use { fis ->
if (loc.first < 0) {
fis.skip(getLength() + loc.first)
} else {
fis.skip(loc.first)
}
fis.read(this)
}
}
}
is ByteArray -> {
val subRangeStart = if (loc.first < 0) {
(getLength() + loc.first).toInt()
} else {
loc.first.toInt()
}
return incoming.sliceArray(subRangeStart until (subRangeStart + loc.second).toInt())
}
else -> {
throw IllegalArgumentException("type ${incoming!!::class} is not supported")
}
}
}
}
companion object { companion object {
private val gcfg: Properties = Properties().apply { private val gcfg: Properties = Properties().apply {
load(Helper::class.java.classLoader.getResourceAsStream("general.cfg")) load(Helper::class.java.classLoader.getResourceAsStream("general.cfg"))
@ -368,10 +437,18 @@ class Helper {
return data return data
} }
fun readFully(data: ByteArray, offset: Long, byteCount: Int): ByteArray {
return data.sliceArray(offset.toInt()..(offset + byteCount).toInt())
}
fun readFully(fileName: String, coordinate: Pair<Long, Int>): ByteArray { fun readFully(fileName: String, coordinate: Pair<Long, Int>): ByteArray {
return readFully(fileName, coordinate.first, coordinate.second) return readFully(fileName, coordinate.first, coordinate.second)
} }
fun readFully(data: ByteArray, coordinate: Pair<Long, Int>): ByteArray {
return readFully(data, coordinate.first, coordinate.second)
}
private val log = LoggerFactory.getLogger("Helper") private val log = LoggerFactory.getLogger("Helper")
} }
} }

@ -0,0 +1,41 @@
package cfig.helper
import org.junit.Assert.assertEquals
import org.junit.Test
import java.io.File
class CryptoHelperTest {
private fun checkRsaPair(privKey: String) {
val pubKey = "$privKey.pub"
val priv = CryptoHelperTest::class.java.classLoader.getResource(privKey).file
val privK = CryptoHelper.KeyBox.parse4(File(priv).readBytes())
assertEquals(org.bouncycastle.asn1.pkcs.RSAPrivateKey::class, privK.clazz)
val pub = CryptoHelperTest::class.java.classLoader.getResource(pubKey).file
val pubK = CryptoHelper.KeyBox.parse4(File(pub).readBytes())
assertEquals(java.security.interfaces.RSAPublicKey::class, pubK.clazz)
assertEquals(
(privK.key as org.bouncycastle.asn1.pkcs.RSAPrivateKey).modulus,
(pubK.key as java.security.interfaces.RSAPublicKey).modulus
)
assertEquals(
(privK.key as org.bouncycastle.asn1.pkcs.RSAPrivateKey).publicExponent,
(pubK.key as java.security.interfaces.RSAPublicKey).publicExponent
)
}
//@Test
fun parsePemRSA() {
checkRsaPair("pem.rsa/rsa.2048")
checkRsaPair("pem.rsa/rsa.4096")
checkRsaPair("pem.rsa/rsa.8192")
}
fun geographicalHash() {
val f = "/home/work/boot/payload.bin"
val dg = CryptoHelper.Hasher.hash(f, listOf(Pair(0, 1862657060)), "sha-256")
println(Helper.toHexString(dg))
}
}

@ -17,6 +17,7 @@ package cfig.helper
import cfig.helper.ZipHelper.Companion.dumpEntry import cfig.helper.ZipHelper.Companion.dumpEntry
import org.apache.commons.compress.archivers.zip.ZipFile import org.apache.commons.compress.archivers.zip.ZipFile
import org.junit.After import org.junit.After
import org.junit.Assert
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.io.File import java.io.File
@ -49,4 +50,14 @@ class ZipHelperTest {
it.dumpEntry("webview.log", File("out/webview.log")) it.dumpEntry("webview.log", File("out/webview.log"))
} }
} }
@Test
fun testDataSrc() {
if (File("/proc/cpuinfo").exists()) {
val ds1 = Helper.DataSrc("/proc/cpuinfo")
Assert.assertTrue(ds1.readFully(0L..31).contentEquals(ds1.readFully(Pair(0, 32))))
val d2 = Helper.DataSrc(ds1.readFully(0L..31))
Assert.assertTrue(d2.readFully(0..15L).contentEquals(d2.readFully(Pair(0, 16))))
}
}
} }
Loading…
Cancel
Save