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
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."""
avbtool_cmd = ['avbtool', 'add_hash_footer', '--image', image,
@ -143,7 +143,6 @@ def add_avb_footer(image, partition_size, extra_footer_args):
else:
avbtool_cmd.extend(['--dynamic_partition_size'])
avbtool_cmd.extend(extra_footer_args)
subprocess.check_call(avbtool_cmd)
@ -161,24 +160,12 @@ 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.
"""
def load_gki_info_file(gki_info_file, extra_args):
"""Loads extra args from |gki_info_file| into |extra_args|."""
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):
@ -219,8 +206,6 @@ def parse_cmdline():
# 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()
@ -233,21 +218,13 @@ def parse_cmdline():
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)
load_gki_info_file(args.gki_info, args.extra_args)
return args
def certify_bootimg(boot_img, output_img, algorithm, key, extra_args,
extra_footer_args):
def certify_bootimg(boot_img, output_img, algorithm, key, extra_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')
@ -257,27 +234,26 @@ 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, extra_footer_args)
add_avb_footer(boot_tmp, avb_partition_size)
# We're done, copy the temp image to the final output.
shutil.copy2(boot_tmp, output_img)
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."""
with tempfile.TemporaryDirectory() as unpack_dir:
shutil.unpack_archive(boot_img_archive, unpack_dir)
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)
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)} ...')
certify_bootimg(boot_img=boot_img, output_img=boot_img,
algorithm=algorithm, key=key, extra_args=extra_args,
extra_footer_args=extra_footer_args)
algorithm=algorithm, key=key, extra_args=extra_args)
print(f'Making certified archive: {output_archive}')
archive_file_name, archive_format = (
@ -299,11 +275,10 @@ def main():
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)
args.algorithm, args.key, args.extra_args)
else:
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__':

@ -82,8 +82,8 @@ def generate_test_boot_image_archive(archive_file_name, archive_format,
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)].
[('boot.img', 4096, 4 * 1024),
('boot-lz4.img', 8192, 8 * 1024)].
gki_info: the file content to be written into 'gki-info.txt' in the
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):
"""Extracts boot images and signatures of a boot images archive.
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
Suppose there are two boot images in |boot_img_archive|: boot.img
and boot-lz4.img. This function then extracts each boot*.img and
their signatures as:
- |output_dir|/boot-1.0.img
- |output_dir|/boot-2.0.img
- |output_dir|/boot-1.0/boot_signature1
- |output_dir|/boot-1.0/boot_signature2
- |output_dir|/boot-2.0/boot_signature1
- |output_dir|/boot-2.0/boot_signature2
- |output_dir|/boot.img
- |output_dir|/boot-lz4.img
- |output_dir|/boot/boot_signature1
- |output_dir|/boot/boot_signature2
- |output_dir|/boot-lz4/boot_signature1
- |output_dir|/boot-lz4/boot_signature2
"""
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]
signature_output_dir = os.path.join(output_dir, img_name)
os.mkdir(signature_output_dir, 0o777)
@ -219,197 +219,6 @@ 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'
@ -576,7 +385,7 @@ class CertifyBootimgTest(unittest.TestCase):
" 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'
'Header Block: 256 bytes\n'
'Authentication Block: 576 bytes\n'
@ -595,8 +404,8 @@ class CertifyBootimgTest(unittest.TestCase):
' Partition Name: boot\n' # boot
' Salt: d00df00d\n'
' Digest: '
'88465e463bffb9f7dfc0c1f46d01bcf3'
'15f7693e19bd188a0ca1feca2ed7b9df\n'
'30208b4d0a6d16db47fc13c9527bfe81'
'a168d3b3940325d1ca8d3439792bfe18\n'
' Flags: 0\n'
" Prop: gki -> 'nice'\n"
" Prop: space -> 'nice to meet you'\n"
@ -607,7 +416,7 @@ class CertifyBootimgTest(unittest.TestCase):
" 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'
'Header Block: 256 bytes\n'
'Authentication Block: 576 bytes\n'
@ -626,8 +435,8 @@ class CertifyBootimgTest(unittest.TestCase):
' Partition Name: generic_kernel\n' # generic_kernel
' Salt: d00df00d\n'
' Digest: '
'14ac8d0d233e57a317acd05cd458f2bb'
'cc78725ef9f66c1b38e90697fb09d943\n'
'd4c8847e7d9900a98f77e1f0b5272854'
'7bf9c1e428fea500d419275f72ec5bd6\n'
' Flags: 0\n'
" Prop: gki -> 'nice'\n"
" Prop: space -> 'nice to meet you'\n"
@ -638,7 +447,7 @@ class CertifyBootimgTest(unittest.TestCase):
" 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'
'Header Block: 256 bytes\n'
'Authentication Block: 576 bytes\n'
@ -657,8 +466,8 @@ class CertifyBootimgTest(unittest.TestCase):
' Partition Name: boot\n' # boot
' Salt: d00df00d\n'
' Digest: '
'3e6a9854a9d2350a7071083bc3f37376'
'37573fd87b1c72b146cb4870ac6af36f\n'
'9d3a0670a9fd3de66e940117ef97700f'
'ed5fd1c6fb90798fd3873af45fc91cb4\n'
' Flags: 0\n'
" Prop: gki -> 'nice'\n"
" Prop: space -> 'nice to meet you'\n"
@ -669,7 +478,7 @@ class CertifyBootimgTest(unittest.TestCase):
" 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'
'Header Block: 256 bytes\n'
'Authentication Block: 576 bytes\n'
@ -688,8 +497,8 @@ class CertifyBootimgTest(unittest.TestCase):
' Partition Name: generic_kernel\n' # generic_kernel
' Salt: d00df00d\n'
' Digest: '
'92fb8443cd284b67a4cbf5ce00348b50'
'1c657e0aedf4e2181c92ad7fc8b5224f\n'
'7d109e3dccca9e30e04249162d07e58c'
'62fdf269804b35857b956fba339b2679\n'
' Flags: 0\n'
" Prop: gki -> 'nice'\n"
" Prop: space -> 'nice to meet you'\n"
@ -700,7 +509,7 @@ class CertifyBootimgTest(unittest.TestCase):
" 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'
'Header Block: 256 bytes\n'
'Authentication Block: 576 bytes\n'
@ -719,14 +528,14 @@ class CertifyBootimgTest(unittest.TestCase):
' Partition Name: boot\n' # boot
' Salt: d00df00d\n'
' Digest: '
'9b9cd845a367d7fc9b61d6ac02b0e7c9'
'dc3d3b219abf60dd6e19359f0353c917\n'
'6fcddc6167ae3c2037b424d35c3ef107'
'f586510dbb2d652d7c08b88e6ea52fc6\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
self._EXPECTED_BOOT_GZ_SIGNATURE2_RSA4096 = ( # pylint: disable=C0103
'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n'
'Authentication Block: 576 bytes\n'
@ -745,8 +554,8 @@ class CertifyBootimgTest(unittest.TestCase):
' Partition Name: generic_kernel\n' # generic_kernel
' Salt: d00df00d\n'
' Digest: '
'0cd7d331ed9b32dcd92f00e2cac75595'
'52199170afe788a8fcf1954f9ea072d0\n'
'7a6a43eb4048b783346fb6d039103647'
'6c4313146da521467af282dff1838d0e\n'
' Flags: 0\n'
" Prop: gki -> 'nice'\n"
" Prop: space -> 'nice to meet you'\n"
@ -838,8 +647,6 @@ 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)
@ -848,13 +655,7 @@ 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,
@ -871,8 +672,6 @@ 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)
@ -881,13 +680,7 @@ 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,
@ -907,11 +700,7 @@ class CertifyBootimgTest(unittest.TestCase):
'-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')
'--prop GKI_INFO:"added here"\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)
@ -926,8 +715,6 @@ 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"',
'--gki_info', gki_info_path,
'--output', boot_certified_img,
]
@ -938,12 +725,6 @@ class CertifyBootimgTest(unittest.TestCase):
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,
@ -990,17 +771,13 @@ class CertifyBootimgTest(unittest.TestCase):
'-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')
'--prop SPACE:"nice to meet you"\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.img', 8 * 1024, 128 * 1024),
('boot-lz4.img', 16 * 1024, 256 * 1024)],
gki_info)
# Certify the boot image archive, with a RSA4096 key.
@ -1013,8 +790,6 @@ 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_certified_img_archive,
]
subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir)
@ -1023,31 +798,24 @@ class CertifyBootimgTest(unittest.TestCase):
temp_out_dir)
# Checks an AVB footer exists and the image size remains.
boot_1_img = os.path.join(temp_out_dir, 'boot-1.0.img')
self.assertTrue(has_avb_footer(boot_1_img))
self.assertEqual(os.path.getsize(boot_1_img), 128 * 1024)
boot_img = os.path.join(temp_out_dir, 'boot.img')
self.assertTrue(has_avb_footer(boot_img))
self.assertEqual(os.path.getsize(boot_img), 128 * 1024)
boot_2_img = os.path.join(temp_out_dir, 'boot-2.0.img')
self.assertTrue(has_avb_footer(boot_2_img))
self.assertEqual(os.path.getsize(boot_2_img), 256 * 1024)
boot_lz4_img = os.path.join(temp_out_dir, 'boot-lz4.img')
self.assertTrue(has_avb_footer(boot_lz4_img))
self.assertEqual(os.path.getsize(boot_lz4_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':
self._EXPECTED_BOOT_1_0_SIGNATURE1_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})
{'boot/boot_signature1':
self._EXPECTED_BOOT_SIGNATURE1_RSA4096,
'boot/boot_signature2':
self._EXPECTED_BOOT_SIGNATURE2_RSA4096,
'boot-lz4/boot_signature1':
self._EXPECTED_BOOT_LZ4_SIGNATURE1_RSA4096,
'boot-lz4/boot_signature2':
self._EXPECTED_BOOT_LZ4_SIGNATURE2_RSA4096})
def test_certify_bootimg_archive_without_gki_info(self):
"""Tests certify_bootimg for a boot images archive."""
@ -1060,7 +828,7 @@ class CertifyBootimgTest(unittest.TestCase):
boot_img_archive_name,
'zip',
# 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)
# Certify the boot image archive, with a RSA4096 key.
boot_certified_img_archive = os.path.join(
@ -1072,8 +840,6 @@ 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_certified_img_archive,
]
subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir)
@ -1084,7 +850,7 @@ class CertifyBootimgTest(unittest.TestCase):
boot_img_archive_name,
'tar',
# 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'
'c=d\n')
# Certify the boot image archive, with a RSA4096 key.
@ -1097,8 +863,6 @@ 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_certified_img_archive2,
]
subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir)
@ -1107,22 +871,16 @@ class CertifyBootimgTest(unittest.TestCase):
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')
boot_3_img = os.path.join(temp_out_dir, 'boot-gz.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})
{'boot-gz/boot_signature1':
self._EXPECTED_BOOT_GZ_SIGNATURE1_RSA4096,
'boot-gz/boot_signature2':
self._EXPECTED_BOOT_GZ_SIGNATURE2_RSA4096})
# 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 += additional_avb_args
print(avbtool_cmd)
subprocess.check_call(avbtool_cmd)

@ -19,19 +19,19 @@ import avb.blob.AuthBlob
import avb.blob.AuxBlob
import avb.blob.Footer
import avb.blob.Header
import avb.desc.*
import avb.desc.UnknownDescriptor
import cfig.Avb
import cfig.helper.Helper
import cfig.helper.Helper.Companion.paddingWith
import cfig.helper.Helper.DataSrc
import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.commons.codec.binary.Hex
import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
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(
var header: Header? = null,
@ -64,7 +64,7 @@ class AVBInfo(
public_key_metadata_size = auxBlob!!.pubkeyMeta?.pkmd?.size?.toLong() ?: 0L
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()
//2 - auth blob
val authBlob = AuthBlob.createBlob(headerBlob, newAuxBlob, alg.name)
@ -88,33 +88,29 @@ class AVBInfo(
private val log = LoggerFactory.getLogger(AVBInfo::class.java)
private val mapper = ObjectMapper()
private data class Glance(
data class Glance(
var footer: Footer?,
var vbMetaOffset: Long
)
private fun imageGlance(imageFile: String): Glance {
private fun imageGlance(dataSrc: DataSrc<*>): Glance {
val ret = Glance(null, 0)
// footer
FileInputStream(imageFile).use { fis ->
fis.skip(File(imageFile).length() - Footer.SIZE)
try {
ret.footer = Footer(fis)
ret.vbMetaOffset = ret.footer!!.vbMetaOffset
log.info("$imageFile: $ret.footer")
} catch (e: IllegalArgumentException) {
log.info("image $imageFile has no AVB Footer")
}
try {
ret.footer = Footer(dataSrc.readFully(Pair(-Footer.SIZE.toLong(), Footer.SIZE)))
ret.vbMetaOffset = ret.footer!!.vbMetaOffset
log.info("${dataSrc.getName()}: $ret.footer")
} catch (e: IllegalArgumentException) {
log.info("image ${dataSrc.getName()} has no AVB Footer")
}
return ret
}
fun parseFrom(imageFile: String): AVBInfo {
log.info("parseFrom($imageFile) ...")
fun parseFrom(dataSrc: DataSrc<*>): AVBInfo {
log.info("parseFrom(${dataSrc.getName()}) ...")
// glance
val (footer, vbMetaOffset) = imageGlance(imageFile)
val (footer, vbMetaOffset) = imageGlance(dataSrc)
// 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))
val atlas = mutableMapOf<String, Pair<Long, Int>>()
@ -130,9 +126,9 @@ class AVBInfo(
val ai = AVBInfo(vbMetaHeader, null, AuxBlob(), footer)
// Auth blob
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))
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))
ai.authBlob = AuthBlob()
ai.authBlob!!.offset = atlas["auth"]!!.first
@ -141,10 +137,10 @@ class AVBInfo(
ai.authBlob!!.signature = Hex.encodeHexString(bb)
}
// aux
val rawAuxBlob = Helper.readFully(imageFile, atlas["aux"]!!)
val rawAuxBlob = dataSrc.readFully(atlas["aux"]!!)
// aux - desc
if (vbMetaHeader.descriptors_size > 0) {
val descriptors = UnknownDescriptor.parseDescriptors2(
val descriptors = UnknownDescriptor.parseDescriptors(
ByteArrayInputStream(
rawAuxBlob.copyOfRange(vbMetaHeader.descriptors_offset.toInt(), rawAuxBlob.size)
),
@ -176,8 +172,8 @@ class AVBInfo(
)
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
}
}
}
}

@ -20,10 +20,11 @@ import avb.blob.AuthBlob
import avb.blob.AuxBlob
import avb.blob.Footer
import avb.blob.Header
import avb.desc.*
import avb.desc.HashDescriptor
import cfig.helper.CryptoHelper
import cfig.helper.Helper
import cfig.helper.Helper.Companion.paddingWith
import cfig.helper.Helper.DataSrc
import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.commons.codec.binary.Hex
import org.apache.commons.exec.CommandLine
@ -106,7 +107,7 @@ class Avb {
log.info("4/4 Appending AVB footer (${footerBlobWithPadding.size} bytes)...")
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.")
}
@ -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 {
private val log = LoggerFactory.getLogger(Avb::class.java)
const val BLOCK_SIZE = 4096
@ -297,8 +205,8 @@ class Avb {
it.auxBlob!!.hashDescriptors.get(0).partition_name
}
//read hashDescriptor from image
val newHashDesc = AVBInfo.parseFrom("$fileName.signed")
assert(newHashDesc.auxBlob!!.hashDescriptors.size == 1)
val newHashDesc = AVBInfo.parseFrom(DataSrc("$fileName.signed"))
check(newHashDesc.auxBlob!!.hashDescriptors.size == 1)
var seq = -1 //means not found
//main vbmeta
ObjectMapper().readValue(File(getJsonFileName("vbmeta.img")), AVBInfo::class.java).apply {
@ -327,5 +235,104 @@ class Avb {
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.desc.*
import cc.cfig.io.Struct
import cfig.helper.CryptoHelper
import cfig.helper.Helper
import cc.cfig.io.Struct
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import org.bouncycastle.asn1.pkcs.RSAPrivateKey
import org.slf4j.LoggerFactory
import java.nio.file.Files
import java.nio.file.Paths
import java.io.File
@JsonIgnoreProperties("descriptorSize")
class AuxBlob(
@ -34,7 +33,8 @@ class AuxBlob(
var hashDescriptors: MutableList<HashDescriptor> = mutableListOf(),
var kernelCmdlineDescriptors: MutableList<KernelCmdlineDescriptor> = mutableListOf(),
var chainPartitionDescriptors: MutableList<ChainPartitionDescriptor> = mutableListOf(),
var unknownDescriptors: MutableList<UnknownDescriptor> = mutableListOf()) {
var unknownDescriptors: MutableList<UnknownDescriptor> = mutableListOf()
) {
val descriptorSize: Int
get(): Int {
@ -42,25 +42,26 @@ class AuxBlob(
}
data class PubKeyInfo(
var offset: Long = 0L,
var size: Long = 0L,
var pubkey: ByteArray = byteArrayOf()
var offset: Long = 0L,
var size: Long = 0L,
var pubkey: ByteArray = byteArrayOf()
)
data class PubKeyMetadataInfo(
var offset: Long = 0L,
var size: Long = 0L,
var pkmd: ByteArray = byteArrayOf()
var offset: Long = 0L,
var size: Long = 0L,
var pkmd: ByteArray = byteArrayOf()
)
private fun encodeDescriptors(): ByteArray {
return mutableListOf<Descriptor>().let { descList ->
arrayOf(this.propertyDescriptors, //tag 0
this.hashTreeDescriptors, //tag 1
this.hashDescriptors, //tag 2
this.kernelCmdlineDescriptors, //tag 3
this.chainPartitionDescriptors, //tag 4
this.unknownDescriptors //tag X
arrayOf(
this.propertyDescriptors, //tag 0
this.hashTreeDescriptors, //tag 1
this.hashDescriptors, //tag 2
this.kernelCmdlineDescriptors, //tag 3
this.chainPartitionDescriptors, //tag 4
this.unknownDescriptors //tag X
).forEach { typedList ->
typedList.forEach { descList.add(it) }
}
@ -96,8 +97,9 @@ class AuxBlob(
}
val auxSize = Helper.round_to_multiple(
(encodedDesc.size + encodedKey.size + encodedPkmd.size).toLong(),
64)
(encodedDesc.size + encodedKey.size + encodedPkmd.size).toLong(),
64
)
return Struct("${auxSize}b").pack(Helper.join(encodedDesc, encodedKey, encodedPkmd))
}
@ -135,13 +137,10 @@ class AuxBlob(
fun encodePubKey(alg: Algorithm, key: ByteArray? = null): ByteArray {
var encodedKey = byteArrayOf()
if (alg.public_key_num_bytes > 0) {
var algKey: ByteArray? = key
if (key == null) {
algKey = Files.readAllBytes((Paths.get(alg.defaultKey)))
}
val rsa = CryptoHelper.KeyBox.parse4(algKey!!).key as RSAPrivateKey //BC RSA
val algKey: ByteArray = key ?: File(alg.defaultKey).readBytes()
val rsa = CryptoHelper.KeyBox.parse4(algKey).key as RSAPrivateKey //BC RSA
encodedKey = CryptoHelper.KeyBox.encodeRSAkey(rsa)
assert(alg.public_key_num_bytes == encodedKey.size)
check(alg.public_key_num_bytes == encodedKey.size)
} else {
log.info("encodePubKey(): No key to encode for algorithm " + alg.name)
}

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

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

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

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

@ -72,7 +72,7 @@ class KernelCmdlineDescriptor(
const val flagHashTreeDisabled = 2
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 val log = LoggerFactory.getLogger(UnknownDescriptor::class.java)
fun parseDescriptors(stream: InputStream, totalSize: Long): List<UnknownDescriptor> {
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> {
fun parseDescriptors(stream: InputStream, totalSize: Long): List<Descriptor> {
log.debug("Parse descriptors stream, SIZE = $totalSize")
val ret: MutableList<Descriptor> = mutableListOf()
var currentSize = 0L
@ -120,7 +97,7 @@ class UnknownDescriptor(var data: ByteArray = byteArrayOf()) : Descriptor(0, 0,
}
init {
assert(SIZE == Struct(FORMAT).calcSize())
check(SIZE == Struct(FORMAT).calcSize())
}
}
}

@ -59,7 +59,7 @@ open class BootHeaderV2(
}
log.warn("BootImgHeader constructor")
val info = Struct(FORMAT_STRING).unpack(iS)
assert(20 == info.size)
check(20 == info.size)
if (info[0] != magic) {
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()
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)) {
"header size ${this.headerSize} illegal"
}
@ -109,7 +109,7 @@ open class BootHeaderV2(
val pageSizeChoices: MutableSet<Long> = mutableSetOf<Long>().apply {
(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(
magic,
//10I
@ -168,8 +168,7 @@ open class BootHeaderV2(
const val BOOT_IMAGE_HEADER_V0_SIZE = 0
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.v3.VendorBoot
import cfig.helper.Helper
import cfig.helper.Helper.DataSrc
import cfig.packable.VBMetaParser
import cfig.utils.EnvironmentVerifier
import com.fasterxml.jackson.databind.ObjectMapper
@ -225,7 +226,7 @@ data class BootV2(
fun extractVBMeta(): BootV2 {
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()) {
log.warn("Found vbmeta.img, parsing ...")
VBMetaParser().unpack("vbmeta.img")

@ -21,6 +21,7 @@ import cfig.bootimg.Common.Companion.deleleIfExists
import cfig.bootimg.Signer
import cfig.bootimg.v3.VendorBoot
import cfig.helper.Helper
import cfig.helper.Helper.DataSrc
import cfig.packable.VBMetaParser
import cfig.utils.EnvironmentVerifier
import com.fasterxml.jackson.databind.ObjectMapper
@ -244,7 +245,7 @@ data class BootV2Dialects(
fun extractVBMeta(): BootV2Dialects {
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()) {
log.warn("Found vbmeta.img, parsing ...")
VBMetaParser().unpack("vbmeta.img")

@ -36,7 +36,7 @@ class BootHeaderV3(
}
log.warn("BootImgHeaderV3/V4 constructor")
val info = Struct(FORMAT_STRING).unpack(iS)
assert(12 == info.size)
check(12 == info.size)
if (info[0] != magic) {
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.cmdline = info[10] as String
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 {
@ -106,7 +106,7 @@ class BootHeaderV3(
const val pageSize: Int = 4096
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 "
}
}

@ -23,6 +23,7 @@ import cfig.bootimg.Common.Companion.deleleIfExists
import cfig.bootimg.Common.Companion.getPaddingSize
import cfig.bootimg.Signer
import cfig.helper.Helper
import cfig.helper.Helper.DataSrc
import cfig.packable.VBMetaParser
import com.fasterxml.jackson.databind.ObjectMapper
import de.vandermeer.asciitable.AsciiTable
@ -231,17 +232,6 @@ data class BootV3(
this.ramdisk.file = this.ramdisk.file + ".$fmt"
}
//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
mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this)
@ -249,8 +239,9 @@ data class BootV3(
}
fun extractVBMeta(): BootV3 {
// vbmeta in image
try {
AVBInfo.parseFrom(info.output).dumpDefault(info.output)
AVBInfo.parseFrom(DataSrc(info.output)).dumpDefault(info.output)
if (File("vbmeta.img").exists()) {
log.warn("Found vbmeta.img, parsing ...")
VBMetaParser().unpack("vbmeta.img")
@ -259,6 +250,51 @@ data class BootV3(
log.warn(e.message)
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
}

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

@ -42,7 +42,7 @@ class VendorBootHeader(
}
log.warn("VendorBootHeader constructor")
val info = Struct(FORMAT_STRING).unpack(iS)
assert(16 == info.size)
check(16 == info.size)
if (info[0] != magic) {
throw IllegalArgumentException("stream doesn't look like Android Vendor Boot Image")
}
@ -134,7 +134,7 @@ class VendorBootHeader(
"I" //[v4] bootconfig size
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
init {
assert(SIZE == Struct(FORMAT_STRING).calcSize())
check(SIZE == Struct(FORMAT_STRING).calcSize())
}
/*
@ -188,7 +188,7 @@ data class MiscImage(
const val SIZE = 64
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.BootV2Dialects
import cfig.bootimg.v3.BootV3
import cfig.helper.Helper
import cfig.helper.Helper.Companion.deleteIfExists
import com.fasterxml.jackson.databind.ObjectMapper
import de.vandermeer.asciitable.AsciiTable
@ -119,6 +120,12 @@ class BootImgParser : IPackable {
}
override fun `@verify`(fileName: String) {
File(Helper.prop("workDir")).let {
if (!it.exists()) {
it.mkdirs()
}
}
super.`@verify`(fileName)
}

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

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

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

@ -6,7 +6,7 @@ verity_pk8 = aosp/security/verity.pk8
verity_pem = aosp/security/verity.x509.pem
kernelVersionFile = build/unzip_boot/kernel_version.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
dtboMaker = aosp/system/libufdt/utils/src/mkdtboimg.py
payloadDir = build/payload/

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

@ -38,6 +38,75 @@ class Helper {
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 {
private val gcfg: Properties = Properties().apply {
load(Helper::class.java.classLoader.getResourceAsStream("general.cfg"))
@ -368,10 +437,18 @@ class Helper {
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 {
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")
}
}

@ -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 org.apache.commons.compress.archivers.zip.ZipFile
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import java.io.File
@ -46,7 +47,17 @@ class ZipHelperTest {
fun dumpEntry() {
val zf = ZipHelperTest::class.java.classLoader.getResource("appcompat.zip").file
ZipFile(zf).use {
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