diff --git a/aosp/make/target/product/gsi/testkey_rsa2048.pem b/aosp/make/target/product/gsi/testkey_rsa2048.pem new file mode 100644 index 0000000..64de31c --- /dev/null +++ b/aosp/make/target/product/gsi/testkey_rsa2048.pem @@ -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----- diff --git a/aosp/build/tools/extract_kernel.py b/aosp/make/tools/extract_kernel.py similarity index 100% rename from aosp/build/tools/extract_kernel.py rename to aosp/make/tools/extract_kernel.py diff --git a/aosp/system/tools/mkbootimg/gki/certify_bootimg.py b/aosp/system/tools/mkbootimg/gki/certify_bootimg.py index 2c3d80e..39dcaeb 100755 --- a/aosp/system/tools/mkbootimg/gki/certify_bootimg.py +++ b/aosp/system/tools/mkbootimg/gki/certify_bootimg.py @@ -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__': diff --git a/aosp/system/tools/mkbootimg/gki/certify_bootimg_test.py b/aosp/system/tools/mkbootimg/gki/certify_bootimg_test.py index 779c46f..fd7473b 100644 --- a/aosp/system/tools/mkbootimg/gki/certify_bootimg_test.py +++ b/aosp/system/tools/mkbootimg/gki/certify_bootimg_test.py @@ -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 diff --git a/aosp/system/tools/mkbootimg/gki/generate_gki_certificate.py b/aosp/system/tools/mkbootimg/gki/generate_gki_certificate.py index 2797cca..17a40bc 100755 --- a/aosp/system/tools/mkbootimg/gki/generate_gki_certificate.py +++ b/aosp/system/tools/mkbootimg/gki/generate_gki_certificate.py @@ -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) diff --git a/bbootimg/src/main/kotlin/avb/AVBInfo.kt b/bbootimg/src/main/kotlin/avb/AVBInfo.kt index cb61182..13b5d5c 100644 --- a/bbootimg/src/main/kotlin/avb/AVBInfo.kt +++ b/bbootimg/src/main/kotlin/avb/AVBInfo.kt @@ -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>() @@ -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 } } -} \ No newline at end of file +} diff --git a/bbootimg/src/main/kotlin/avb/Avb.kt b/bbootimg/src/main/kotlin/avb/Avb.kt index e4d146b..d3b9b14 100644 --- a/bbootimg/src/main/kotlin/avb/Avb.kt +++ b/bbootimg/src/main/kotlin/avb/Avb.kt @@ -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 { - val ret: Array = 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 { + val ret: Array = 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 + } } } diff --git a/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt b/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt index 4b41912..7a5ca83 100644 --- a/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt +++ b/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt @@ -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 = mutableListOf(), var kernelCmdlineDescriptors: MutableList = mutableListOf(), var chainPartitionDescriptors: MutableList = mutableListOf(), - var unknownDescriptors: MutableList = mutableListOf()) { + var unknownDescriptors: MutableList = 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().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) } diff --git a/bbootimg/src/main/kotlin/avb/blob/Footer.kt b/bbootimg/src/main/kotlin/avb/blob/Footer.kt index 1e0a7a4..c33701d 100644 --- a/bbootimg/src/main/kotlin/avb/blob/Footer.kt +++ b/bbootimg/src/main/kotlin/avb/blob/Footer.kt @@ -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()) } } } diff --git a/bbootimg/src/main/kotlin/avb/blob/Header.kt b/bbootimg/src/main/kotlin/avb/blob/Header.kt index 10c4dee..6275c5d 100644 --- a/bbootimg/src/main/kotlin/avb/blob/Header.kt +++ b/bbootimg/src/main/kotlin/avb/blob/Header.kt @@ -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()) } } } diff --git a/bbootimg/src/main/kotlin/avb/desc/ChainPartitionDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/ChainPartitionDescriptor.kt index 90d8352..27d44c2 100644 --- a/bbootimg/src/main/kotlin/avb/desc/ChainPartitionDescriptor.kt +++ b/bbootimg/src/main/kotlin/avb/desc/ChainPartitionDescriptor.kt @@ -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 = 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" diff --git a/bbootimg/src/main/kotlin/avb/desc/HashDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/HashDescriptor.kt index 4c932c6..a6bfe87 100644 --- a/bbootimg/src/main/kotlin/avb/desc/HashDescriptor.kt +++ b/bbootimg/src/main/kotlin/avb/desc/HashDescriptor.kt @@ -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 diff --git a/bbootimg/src/main/kotlin/avb/desc/HashTreeDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/HashTreeDescriptor.kt index a410024..e5b07fd 100644 --- a/bbootimg/src/main/kotlin/avb/desc/HashTreeDescriptor.kt +++ b/bbootimg/src/main/kotlin/avb/desc/HashTreeDescriptor.kt @@ -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)) { diff --git a/bbootimg/src/main/kotlin/avb/desc/KernelCmdlineDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/KernelCmdlineDescriptor.kt index 394f37b..a71cc92 100644 --- a/bbootimg/src/main/kotlin/avb/desc/KernelCmdlineDescriptor.kt +++ b/bbootimg/src/main/kotlin/avb/desc/KernelCmdlineDescriptor.kt @@ -72,7 +72,7 @@ class KernelCmdlineDescriptor( const val flagHashTreeDisabled = 2 init { - assert(SIZE == Struct(FORMAT_STRING).calcSize()) + check(SIZE == Struct(FORMAT_STRING).calcSize()) } } } diff --git a/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt index e6f28c8..b1954ea 100644 --- a/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt +++ b/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt @@ -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 { - log.debug("Parse descriptors stream, SIZE = $totalSize") - val ret: MutableList = 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 { + fun parseDescriptors(stream: InputStream, totalSize: Long): List { log.debug("Parse descriptors stream, SIZE = $totalSize") val ret: MutableList = 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()) } } } diff --git a/bbootimg/src/main/kotlin/bootimg/v2/BootHeaderV2.kt b/bbootimg/src/main/kotlin/bootimg/v2/BootHeaderV2.kt index 59d6c78..f242b5a 100644 --- a/bbootimg/src/main/kotlin/bootimg/v2/BootHeaderV2.kt +++ b/bbootimg/src/main/kotlin/bootimg/v2/BootHeaderV2.kt @@ -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 = mutableSetOf().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()) } - } } diff --git a/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt b/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt index 52dce79..34cd980 100644 --- a/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt +++ b/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt @@ -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") diff --git a/bbootimg/src/main/kotlin/bootimg/v2/BootV2Dialects.kt b/bbootimg/src/main/kotlin/bootimg/v2/BootV2Dialects.kt index 807fab0..328d5a1 100644 --- a/bbootimg/src/main/kotlin/bootimg/v2/BootV2Dialects.kt +++ b/bbootimg/src/main/kotlin/bootimg/v2/BootV2Dialects.kt @@ -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") diff --git a/bbootimg/src/main/kotlin/bootimg/v3/BootHeaderV3.kt b/bbootimg/src/main/kotlin/bootimg/v3/BootHeaderV3.kt index 4f79246..d346a4f 100644 --- a/bbootimg/src/main/kotlin/bootimg/v3/BootHeaderV3.kt +++ b/bbootimg/src/main/kotlin/bootimg/v3/BootHeaderV3.kt @@ -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 " } } diff --git a/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt b/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt index 6179e4f..416762f 100644 --- a/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt +++ b/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt @@ -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 } diff --git a/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt b/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt index 3ecdbb7..0847824 100644 --- a/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt +++ b/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt @@ -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") } diff --git a/bbootimg/src/main/kotlin/bootimg/v3/VendorBootHeader.kt b/bbootimg/src/main/kotlin/bootimg/v3/VendorBootHeader.kt index 22788a7..ad7c5d5 100644 --- a/bbootimg/src/main/kotlin/bootimg/v3/VendorBootHeader.kt +++ b/bbootimg/src/main/kotlin/bootimg/v3/VendorBootHeader.kt @@ -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) } } diff --git a/bbootimg/src/main/kotlin/miscimg/MiscImage.kt b/bbootimg/src/main/kotlin/miscimg/MiscImage.kt index 4931906..1178380 100644 --- a/bbootimg/src/main/kotlin/miscimg/MiscImage.kt +++ b/bbootimg/src/main/kotlin/miscimg/MiscImage.kt @@ -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()) } } diff --git a/bbootimg/src/main/kotlin/packable/BootImgParser.kt b/bbootimg/src/main/kotlin/packable/BootImgParser.kt index 35af43e..2c41dc6 100644 --- a/bbootimg/src/main/kotlin/packable/BootImgParser.kt +++ b/bbootimg/src/main/kotlin/packable/BootImgParser.kt @@ -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) } diff --git a/bbootimg/src/main/kotlin/packable/IPackable.kt b/bbootimg/src/main/kotlin/packable/IPackable.kt index 41704cb..fa76b21 100644 --- a/bbootimg/src/main/kotlin/packable/IPackable.kt +++ b/bbootimg/src/main/kotlin/packable/IPackable.kt @@ -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() { diff --git a/bbootimg/src/main/kotlin/packable/VBMetaParser.kt b/bbootimg/src/main/kotlin/packable/VBMetaParser.kt index e014180..aa4b183 100644 --- a/bbootimg/src/main/kotlin/packable/VBMetaParser.kt +++ b/bbootimg/src/main/kotlin/packable/VBMetaParser.kt @@ -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) { diff --git a/bbootimg/src/main/kotlin/utils/Dtbo.kt b/bbootimg/src/main/kotlin/utils/Dtbo.kt index 276e87d..e6cc371 100644 --- a/bbootimg/src/main/kotlin/utils/Dtbo.kt +++ b/bbootimg/src/main/kotlin/utils/Dtbo.kt @@ -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") } diff --git a/bbootimg/src/main/resources/general.cfg b/bbootimg/src/main/resources/general.cfg index ea87eb3..45c5259 100644 --- a/bbootimg/src/main/resources/general.cfg +++ b/bbootimg/src/main/resources/general.cfg @@ -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/ diff --git a/helper/src/main/kotlin/cfig/helper/CryptoHelper.kt b/helper/src/main/kotlin/cfig/helper/CryptoHelper.kt index d8f47ef..707f565 100644 --- a/helper/src/main/kotlin/cfig/helper/CryptoHelper.kt +++ b/helper/src/main/kotlin/cfig/helper/CryptoHelper.kt @@ -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 ) } diff --git a/helper/src/main/kotlin/cfig/helper/Helper.kt b/helper/src/main/kotlin/cfig/helper/Helper.kt index d87c05f..f066cbc 100644 --- a/helper/src/main/kotlin/cfig/helper/Helper.kt +++ b/helper/src/main/kotlin/cfig/helper/Helper.kt @@ -38,6 +38,75 @@ class Helper { var dumpFile: String ) + class DataSrc(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): 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): ByteArray { return readFully(fileName, coordinate.first, coordinate.second) } + fun readFully(data: ByteArray, coordinate: Pair): ByteArray { + return readFully(data, coordinate.first, coordinate.second) + } + private val log = LoggerFactory.getLogger("Helper") } } diff --git a/helper/src/test/kotlin/cfig/helper/CryptoHelperTest.kt b/helper/src/test/kotlin/cfig/helper/CryptoHelperTest.kt new file mode 100644 index 0000000..7e6760f --- /dev/null +++ b/helper/src/test/kotlin/cfig/helper/CryptoHelperTest.kt @@ -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)) + } +} diff --git a/helper/src/test/kotlin/cfig/helper/ZipHelperTest.kt b/helper/src/test/kotlin/cfig/helper/ZipHelperTest.kt index 87d5bd3..3bbfcb7 100644 --- a/helper/src/test/kotlin/cfig/helper/ZipHelperTest.kt +++ b/helper/src/test/kotlin/cfig/helper/ZipHelperTest.kt @@ -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)))) } } } \ No newline at end of file