From fac31f1526282e98dc4e068792663da86ed2406e Mon Sep 17 00:00:00 2001 From: cfig Date: Wed, 27 Mar 2019 14:33:55 +0800 Subject: [PATCH] mass update update avbtool from upstream update kotlin to 1.3.20 support boot image header v2 add integration test --- README.md | 5 +- avb/avbtool | 549 +++++++++++++++--- bbootimg/build.gradle | 2 +- bbootimg/src/main/kotlin/Avb.kt | 115 +++- bbootimg/src/main/kotlin/Helper.kt | 27 +- bbootimg/src/main/kotlin/ImgArgs.kt | 214 ------- bbootimg/src/main/kotlin/ImgInfo.kt | 2 +- bbootimg/src/main/kotlin/Packer.kt | 203 ++----- bbootimg/src/main/kotlin/ParamConfig.kt | 11 + bbootimg/src/main/kotlin/Parser.kt | 272 +++------ bbootimg/src/main/kotlin/R.kt | 47 +- bbootimg/src/main/kotlin/Signer.kt | 72 ++- bbootimg/src/main/kotlin/UnifiedConfig.kt | 181 +++--- bbootimg/src/main/kotlin/avb/AVBInfo.kt | 46 +- bbootimg/src/main/kotlin/avb/AuthBlob.kt | 7 + bbootimg/src/main/kotlin/avb/AuxBlob.kt | 82 +++ bbootimg/src/main/kotlin/avb/Blob.kt | 33 +- .../main/kotlin/avb/ByteArraySerializer.kt | 16 - bbootimg/src/main/kotlin/avb/Footer.kt | 19 +- bbootimg/src/main/kotlin/avb/Header.kt | 23 +- bbootimg/src/main/kotlin/avb/VBMeta.kt | 6 + .../kotlin/avb/desc/PropertyDescriptor.kt | 4 +- .../src/main/kotlin/bootimg/BootImgHeader.kt | 295 ++++++++++ .../src/main/kotlin/bootimg/BootImgInfo.kt | 120 ++++ bbootimg/src/test/kotlin/avb/BlobTest.kt | 6 +- build.gradle | 21 +- doc/layout.md | 146 ++--- integrationTest.py | 67 +++ src/mkbootimg/mkbootimg | 55 +- 29 files changed, 1625 insertions(+), 1021 deletions(-) delete mode 100644 bbootimg/src/main/kotlin/ImgArgs.kt create mode 100644 bbootimg/src/main/kotlin/ParamConfig.kt create mode 100644 bbootimg/src/main/kotlin/avb/AuthBlob.kt create mode 100644 bbootimg/src/main/kotlin/avb/AuxBlob.kt delete mode 100755 bbootimg/src/main/kotlin/avb/ByteArraySerializer.kt create mode 100644 bbootimg/src/main/kotlin/avb/VBMeta.kt create mode 100644 bbootimg/src/main/kotlin/bootimg/BootImgHeader.kt create mode 100644 bbootimg/src/main/kotlin/bootimg/BootImgInfo.kt create mode 100755 integrationTest.py diff --git a/README.md b/README.md index 98a991a..f18918a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Android_boot_image_editor [![Build Status](https://travis-ci.org/cfig/Android_boot_image_editor.svg?branch=master)](https://travis-ci.org/cfig/Android_boot_image_editor) [![License](http://img.shields.io/:license-apache-blue.svg?style=flat-square)](http://www.apache.org/licenses/LICENSE-2.0.html) -[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/Android_boot_image_editor/lobby) This tool focuses on editing Android boot.img(also recovery.img, recovery-two-step.img and vbmeta.img). @@ -38,7 +37,9 @@ Your get the flattened kernel and /root filesystem under **./build/unzip\_boot** ├── boot.img.avb.json (AVB only) ├── bootimg.json (boot image info) ├── kernel - ├── second (2nd bootloader, if exists) + ├── second (2nd bootloader, if exists) + ├── dtb (dtb, if exists) + ├── dtbo (dtbo, if exists) └── root Then you can edit the actual file contents, like rootfs or kernel. diff --git a/avb/avbtool b/avb/avbtool index ec70868..b7af231 100755 --- a/avb/avbtool +++ b/avb/avbtool @@ -65,8 +65,7 @@ class AvbError(Exception): class Algorithm(object): """Contains details about an algorithm. - See the avb_vbmeta_header.h file for more details about - algorithms. + See the avb_vbmeta_image.h file for more details about algorithms. The constant |ALGORITHMS| is a dictionary from human-readable names (e.g 'SHA256_RSA2048') to instances of this class. @@ -545,13 +544,52 @@ def verify_vbmeta_signature(vbmeta_header, vbmeta_blob): modulus = decode_long(modulus_blob) exponent = 65537 - # For now, just use Crypto.PublicKey.RSA to verify the signature. This - # is OK since 'avbtool verify_image' is not expected to run on the - # Android builders (see bug #36809096). - import Crypto.PublicKey.RSA - key = Crypto.PublicKey.RSA.construct((modulus, long(exponent))) - if not key.verify(decode_long(padding_and_digest), - (decode_long(sig_blob), None)): + # We used to have this: + # + # import Crypto.PublicKey.RSA + # key = Crypto.PublicKey.RSA.construct((modulus, long(exponent))) + # if not key.verify(decode_long(padding_and_digest), + # (decode_long(sig_blob), None)): + # return False + # return True + # + # but since 'avbtool verify_image' is used on the builders we don't want + # to rely on Crypto.PublicKey.RSA. Instead just use openssl(1) to verify. + asn1_str = ('asn1=SEQUENCE:pubkeyinfo\n' + '\n' + '[pubkeyinfo]\n' + 'algorithm=SEQUENCE:rsa_alg\n' + 'pubkey=BITWRAP,SEQUENCE:rsapubkey\n' + '\n' + '[rsa_alg]\n' + 'algorithm=OID:rsaEncryption\n' + 'parameter=NULL\n' + '\n' + '[rsapubkey]\n' + 'n=INTEGER:%s\n' + 'e=INTEGER:%s\n' % (hex(modulus).rstrip('L'), hex(exponent).rstrip('L'))) + asn1_tmpfile = tempfile.NamedTemporaryFile() + asn1_tmpfile.write(asn1_str) + asn1_tmpfile.flush() + der_tmpfile = tempfile.NamedTemporaryFile() + p = subprocess.Popen( + ['openssl', 'asn1parse', '-genconf', asn1_tmpfile.name, '-out', der_tmpfile.name, '-noout']) + retcode = p.wait() + if retcode != 0: + raise AvbError('Error generating DER file') + + p = subprocess.Popen( + ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', der_tmpfile.name, '-keyform', 'DER', '-raw'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (pout, perr) = p.communicate(str(sig_blob)) + retcode = p.wait() + if retcode != 0: + raise AvbError('Error verifying data: {}'.format(perr)) + recovered_data = bytearray(pout) + if recovered_data != padding_and_digest: + sys.stderr.write('Signature not correct\n') return False return True @@ -632,6 +670,7 @@ class ImageHandler(object): of the block size. Attributes: + filename: Name of file. is_sparse: Whether the file being operated on is sparse. block_size: The block size, typically 4096. image_size: The size of the unsparsified file. @@ -654,7 +693,7 @@ class ImageHandler(object): Raises: ValueError: If data in the file is invalid. """ - self._image_filename = image_filename + self.filename = image_filename self._read_header() def _read_header(self): @@ -669,7 +708,7 @@ class ImageHandler(object): self.is_sparse = False self.block_size = 4096 self._file_pos = 0 - self._image = open(self._image_filename, 'r+b') + self._image = open(self.filename, 'r+b') self._image.seek(0, os.SEEK_END) self.image_size = self._image.tell() @@ -721,7 +760,7 @@ class ImageHandler(object): chunk_sz*self.block_size, self._image.tell(), None)) - self._image.read(data_sz) + self._image.seek(data_sz, os.SEEK_CUR) elif chunk_type == ImageChunk.TYPE_FILL: if data_sz != 4: @@ -1081,7 +1120,8 @@ class AvbDescriptor(object): ret = desc + self.data + padding return bytearray(ret) - def verify(self, image_dir, image_ext, expected_chain_partitions_map): + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor): """Verifies contents of the descriptor - used in verify_image sub-command. Arguments: @@ -1089,6 +1129,7 @@ class AvbDescriptor(object): image_ext: The extension of the file being verified (e.g. '.img'). expected_chain_partitions_map: A map from partition name to the tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. Returns: True if the descriptor verifies, False otherwise. @@ -1165,7 +1206,8 @@ class AvbPropertyDescriptor(AvbDescriptor): ret = desc + self.key + '\0' + self.value + '\0' + padding return bytearray(ret) - def verify(self, image_dir, image_ext, expected_chain_partitions_map): + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor): """Verifies contents of the descriptor - used in verify_image sub-command. Arguments: @@ -1173,6 +1215,7 @@ class AvbPropertyDescriptor(AvbDescriptor): image_ext: The extension of the file being verified (e.g. '.img'). expected_chain_partitions_map: A map from partition name to the tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. Returns: True if the descriptor verifies, False otherwise. @@ -1325,7 +1368,8 @@ class AvbHashtreeDescriptor(AvbDescriptor): ret = desc + encoded_name + self.salt + self.root_digest + padding return bytearray(ret) - def verify(self, image_dir, image_ext, expected_chain_partitions_map): + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor): """Verifies contents of the descriptor - used in verify_image sub-command. Arguments: @@ -1333,12 +1377,16 @@ class AvbHashtreeDescriptor(AvbDescriptor): image_ext: The extension of the file being verified (e.g. '.img'). expected_chain_partitions_map: A map from partition name to the tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. Returns: True if the descriptor verifies, False otherwise. """ - image_filename = os.path.join(image_dir, self.partition_name + image_ext) - image = ImageHandler(image_filename) + if self.partition_name == '': + image = image_containing_descriptor + else: + image_filename = os.path.join(image_dir, self.partition_name + image_ext) + image = ImageHandler(image_filename) # Generate the hashtree and checks that it matches what's in the file. digest_size = len(hashlib.new(name=self.hash_algorithm).digest()) digest_padding = round_to_pow2(digest_size) - digest_size @@ -1367,7 +1415,7 @@ class AvbHashtreeDescriptor(AvbDescriptor): # takes a long time; and c) is not strictly needed for # verification purposes as we've already verified the root hash. print ('{}: Successfully verified {} hashtree of {} for image of {} bytes' - .format(self.partition_name, self.hash_algorithm, image_filename, + .format(self.partition_name, self.hash_algorithm, image.filename, self.image_size)) return True @@ -1477,7 +1525,8 @@ class AvbHashDescriptor(AvbDescriptor): ret = desc + encoded_name + self.salt + self.digest + padding return bytearray(ret) - def verify(self, image_dir, image_ext, expected_chain_partitions_map): + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor): """Verifies contents of the descriptor - used in verify_image sub-command. Arguments: @@ -1485,12 +1534,16 @@ class AvbHashDescriptor(AvbDescriptor): image_ext: The extension of the file being verified (e.g. '.img'). expected_chain_partitions_map: A map from partition name to the tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. Returns: True if the descriptor verifies, False otherwise. """ - image_filename = os.path.join(image_dir, self.partition_name + image_ext) - image = ImageHandler(image_filename) + if self.partition_name == '': + image = image_containing_descriptor + else: + image_filename = os.path.join(image_dir, self.partition_name + image_ext) + image = ImageHandler(image_filename) data = image.read(self.image_size) ha = hashlib.new(self.hash_algorithm) ha.update(self.salt) @@ -1502,7 +1555,7 @@ class AvbHashDescriptor(AvbDescriptor): format(self.hash_algorithm, image_filename)) return False print ('{}: Successfully verified {} hash of {} for image of {} bytes' - .format(self.partition_name, self.hash_algorithm, image_filename, + .format(self.partition_name, self.hash_algorithm, image.filename, self.image_size)) return True @@ -1582,7 +1635,8 @@ class AvbKernelCmdlineDescriptor(AvbDescriptor): ret = desc + encoded_str + padding return bytearray(ret) - def verify(self, image_dir, image_ext, expected_chain_partitions_map): + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor): """Verifies contents of the descriptor - used in verify_image sub-command. Arguments: @@ -1590,6 +1644,7 @@ class AvbKernelCmdlineDescriptor(AvbDescriptor): image_ext: The extension of the file being verified (e.g. '.img'). expected_chain_partitions_map: A map from partition name to the tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. Returns: True if the descriptor verifies, False otherwise. @@ -1683,7 +1738,8 @@ class AvbChainPartitionDescriptor(AvbDescriptor): ret = desc + encoded_name + self.public_key + padding return bytearray(ret) - def verify(self, image_dir, image_ext, expected_chain_partitions_map): + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor): """Verifies contents of the descriptor - used in verify_image sub-command. Arguments: @@ -1691,6 +1747,7 @@ class AvbChainPartitionDescriptor(AvbDescriptor): image_ext: The extension of the file being verified (e.g. '.img'). expected_chain_partitions_map: A map from partition name to the tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. Returns: True if the descriptor verifies, False otherwise. @@ -1699,7 +1756,7 @@ class AvbChainPartitionDescriptor(AvbDescriptor): if not value: sys.stderr.write('No expected chain partition for partition {}. Use ' '--expected_chain_partition to specify expected ' - 'contents.\n'. + 'contents or --follow_chain_partitions.\n'. format(self.partition_name)) return False rollback_index_location, pk_blob = value @@ -1820,8 +1877,8 @@ class AvbVBMetaHeader(object): """A class for parsing and writing AVB vbmeta images. Attributes: - The attributes correspond to the |AvbVBMetaHeader| struct - defined in avb_vbmeta_header.h. + The attributes correspond to the |AvbVBMetaImageHeader| struct defined in + avb_vbmeta_image.h. """ SIZE = 256 @@ -1960,6 +2017,33 @@ class Avb(object): MAX_VBMETA_SIZE = 64 * 1024 MAX_FOOTER_SIZE = 4096 + def extract_vbmeta_image(self, output, image_filename, padding_size): + """Implements the 'extract_vbmeta_image' command. + + Arguments: + output: Write vbmeta struct to this file. + image_filename: File to extract vbmeta data from (with a footer). + padding_size: If not 0, pads output so size is a multiple of the number. + + Raises: + AvbError: If there's no footer in the image. + """ + image = ImageHandler(image_filename) + + (footer, _, _, _) = self._parse_image(image) + + if not footer: + raise AvbError('Given image does not have a footer.') + + image.seek(footer.vbmeta_offset) + vbmeta_blob = image.read(footer.vbmeta_size) + output.write(vbmeta_blob) + + if padding_size > 0: + padded_size = round_to_multiple(len(vbmeta_blob), padding_size) + padding_needed = padded_size - len(vbmeta_blob) + output.write('\0' * padding_needed) + def erase_footer(self, image_filename, keep_hashtree): """Implements the 'erase_footer' command. @@ -2136,15 +2220,16 @@ class Avb(object): if num_printed == 0: o.write(' (none)\n') - def verify_image(self, image_filename, key_path, expected_chain_partitions): + def verify_image(self, image_filename, key_path, expected_chain_partitions, follow_chain_partitions): """Implements the 'verify_image' command. Arguments: image_filename: Image file to get information from (file object). key_path: None or check that embedded public key matches key at given path. expected_chain_partitions: List of chain partitions to check or None. + follow_chain_partitions: If True, will follows chain partitions even when not + specified with the --expected_chain_partition option """ - expected_chain_partitions_map = {} if expected_chain_partitions: used_locations = {} @@ -2174,11 +2259,11 @@ class Avb(object): offset = 0 if footer: offset = footer.vbmeta_offset - size = (header.SIZE + header.authentication_data_block_size + - header.auxiliary_data_block_size) + image.seek(offset) - vbmeta_blob = image.read(size) - h = AvbVBMetaHeader(vbmeta_blob[0:AvbVBMetaHeader.SIZE]) + vbmeta_blob = image.read(header.SIZE + header.authentication_data_block_size + + header.auxiliary_data_block_size) + alg_name, _ = lookup_algorithm_by_type(header.algorithm_type) if not verify_vbmeta_signature(header, vbmeta_blob): raise AvbError('Signature check failed for {} vbmeta struct {}' @@ -2187,22 +2272,120 @@ class Avb(object): if key_blob: # The embedded public key is in the auxiliary block at an offset. key_offset = AvbVBMetaHeader.SIZE - key_offset += h.authentication_data_block_size - key_offset += h.public_key_offset - key_blob_in_vbmeta = vbmeta_blob[key_offset:key_offset + h.public_key_size] + key_offset += header.authentication_data_block_size + key_offset += header.public_key_offset + key_blob_in_vbmeta = vbmeta_blob[key_offset:key_offset + header.public_key_size] if key_blob != key_blob_in_vbmeta: raise AvbError('Embedded public key does not match given key.') if footer: print ('vbmeta: Successfully verified footer and {} vbmeta struct in {}' - .format(alg_name, image_filename)) + .format(alg_name, image.filename)) else: print ('vbmeta: Successfully verified {} vbmeta struct in {}' - .format(alg_name, image_filename)) + .format(alg_name, image.filename)) for desc in descriptors: - if not desc.verify(image_dir, image_ext, expected_chain_partitions_map): - raise AvbError('Error verifying descriptor.') + if (isinstance(desc, AvbChainPartitionDescriptor) and follow_chain_partitions and + expected_chain_partitions_map.get(desc.partition_name) == None): + # In this case we're processing a chain descriptor but don't have a + # --expect_chain_partition ... however --follow_chain_partitions was + # specified so we shouldn't error out in desc.verify(). + print ('{}: Chained but ROLLBACK_SLOT (which is {}) and KEY (which has sha1 {}) not specified' + .format(desc.partition_name, desc.rollback_index_location, + hashlib.sha1(desc.public_key).hexdigest())) + else: + if not desc.verify(image_dir, image_ext, expected_chain_partitions_map, image): + raise AvbError('Error verifying descriptor.') + # Honor --follow_chain_partitions - add '--' to make the output more readable. + if isinstance(desc, AvbChainPartitionDescriptor) and follow_chain_partitions: + print '--' + chained_image_filename = os.path.join(image_dir, desc.partition_name + image_ext) + self.verify_image(chained_image_filename, key_path, None, False) + + + def calculate_vbmeta_digest(self, image_filename, hash_algorithm, output): + """Implements the 'calculate_vbmeta_digest' command. + + Arguments: + image_filename: Image file to get information from (file object). + hash_algorithm: Hash algorithm used. + output: Output file to write human-readable information to (file object). + """ + + image_dir = os.path.dirname(image_filename) + image_ext = os.path.splitext(image_filename)[1] + + image = ImageHandler(image_filename) + (footer, header, descriptors, image_size) = self._parse_image(image) + offset = 0 + if footer: + offset = footer.vbmeta_offset + size = (header.SIZE + header.authentication_data_block_size + + header.auxiliary_data_block_size) + image.seek(offset) + vbmeta_blob = image.read(size) + + hasher = hashlib.new(name=hash_algorithm) + hasher.update(vbmeta_blob) + + for desc in descriptors: + if isinstance(desc, AvbChainPartitionDescriptor): + ch_image_filename = os.path.join(image_dir, desc.partition_name + image_ext) + ch_image = ImageHandler(ch_image_filename) + (ch_footer, ch_header, ch_descriptors, ch_image_size) = self._parse_image(ch_image) + ch_offset = 0 + ch_size = (ch_header.SIZE + ch_header.authentication_data_block_size + + ch_header.auxiliary_data_block_size) + if ch_footer: + ch_offset = ch_footer.vbmeta_offset + ch_image.seek(ch_offset) + ch_vbmeta_blob = ch_image.read(ch_size) + hasher.update(ch_vbmeta_blob) + + digest = hasher.digest() + output.write('{}\n'.format(digest.encode('hex'))) + + + def calculate_kernel_cmdline(self, image_filename, hashtree_disabled, output): + """Implements the 'calculate_kernel_cmdline' command. + + Arguments: + image_filename: Image file to get information from (file object). + hashtree_disabled: If True, returns the cmdline for hashtree disabled. + output: Output file to write human-readable information to (file object). + """ + + image = ImageHandler(image_filename) + _, _, descriptors, _ = self._parse_image(image) + + image_dir = os.path.dirname(image_filename) + image_ext = os.path.splitext(image_filename)[1] + + cmdline_descriptors = [] + for desc in descriptors: + if isinstance(desc, AvbChainPartitionDescriptor): + ch_image_filename = os.path.join(image_dir, desc.partition_name + image_ext) + ch_image = ImageHandler(ch_image_filename) + _, _, ch_descriptors, _ = self._parse_image(ch_image) + for ch_desc in ch_descriptors: + if isinstance(ch_desc, AvbKernelCmdlineDescriptor): + cmdline_descriptors.append(ch_desc) + elif isinstance(desc, AvbKernelCmdlineDescriptor): + cmdline_descriptors.append(desc) + + kernel_cmdline_snippets = [] + for desc in cmdline_descriptors: + use_cmdline = True + if (desc.flags & AvbKernelCmdlineDescriptor.FLAGS_USE_ONLY_IF_HASHTREE_NOT_DISABLED) != 0: + if hashtree_disabled: + use_cmdline = False + if (desc.flags & AvbKernelCmdlineDescriptor.FLAGS_USE_ONLY_IF_HASHTREE_DISABLED) != 0: + if not hashtree_disabled: + use_cmdline = False + if use_cmdline: + kernel_cmdline_snippets.append(desc.kernel_cmdline) + output.write(' '.join(kernel_cmdline_snippets)) def _parse_image(self, image): @@ -2578,6 +2761,7 @@ class Avb(object): # Add descriptors from other images. if include_descriptors_from_image: + descriptors_dict = dict() for image in include_descriptors_from_image: image_handler = ImageHandler(image.name) (_, image_vbmeta_header, image_descriptors, _) = self._parse_image( @@ -2586,7 +2770,18 @@ class Avb(object): h.bump_required_libavb_version_minor( image_vbmeta_header.required_libavb_version_minor) for desc in image_descriptors: - encoded_descriptors.extend(desc.encode()) + # The --include_descriptors_from_image option is used in some setups + # with images A and B where both A and B contain a descriptor + # for a partition with the same name. Since it's not meaningful + # to include both descriptors, only include the last seen descriptor. + # See bug 76386656 for details. + if hasattr(desc, 'partition_name'): + key = type(desc).__name__ + '_' + desc.partition_name + descriptors_dict[key] = desc.encode() + else: + encoded_descriptors.extend(desc.encode()) + for key in sorted(descriptors_dict.keys()): + encoded_descriptors.extend(descriptors_dict[key]) # Load public key metadata blob, if requested. pkmd_blob = [] @@ -2878,9 +3073,10 @@ class Avb(object): if salt: salt = salt.decode('hex') else: - if salt is None: - # If salt is not explicitly specified, choose a hash - # that's the same size as the hash size. + if salt is None and not use_persistent_digest: + # If salt is not explicitly specified, choose a hash that's the same + # size as the hash size. Don't populate a random salt if this + # descriptor is being created to use a persistent digest on device. hash_size = digest_size salt = open('/dev/urandom').read(hash_size) else: @@ -2986,7 +3182,7 @@ class Avb(object): Arguments: image_filename: File to add the footer to. - partition_size: Size of partition. + partition_size: Size of partition or 0 to put it right at the end. partition_name: Name of partition (without A/B suffix). generate_fec: If True, generate FEC codes. fec_num_roots: Number of roots for FEC. @@ -3037,19 +3233,22 @@ class Avb(object): digest_size = len(hashlib.new(name=hash_algorithm).digest()) digest_padding = round_to_pow2(digest_size) - digest_size - # First, calculate the maximum image size such that an image - # this size + the hashtree + metadata (footer + vbmeta struct) - # fits in |partition_size|. We use very conservative figures for - # metadata. - (_, max_tree_size) = calc_hash_level_offsets( - partition_size, block_size, digest_size + digest_padding) - max_fec_size = 0 - if generate_fec: - max_fec_size = calc_fec_data_size(partition_size, fec_num_roots) - max_metadata_size = (max_fec_size + max_tree_size + - self.MAX_VBMETA_SIZE + - self.MAX_FOOTER_SIZE) - max_image_size = partition_size - max_metadata_size + # If |partition_size| is given (e.g. not 0), calculate the maximum image + # size such that an image this size + the hashtree + metadata (footer + + # vbmeta struct) fits in |partition_size|. We use very conservative figures + # for metadata. + if partition_size > 0: + (_, max_tree_size) = calc_hash_level_offsets( + partition_size, block_size, digest_size + digest_padding) + max_fec_size = 0 + if generate_fec: + max_fec_size = calc_fec_data_size(partition_size, fec_num_roots) + max_metadata_size = (max_fec_size + max_tree_size + + self.MAX_VBMETA_SIZE + + self.MAX_FOOTER_SIZE) + max_image_size = partition_size - max_metadata_size + else: + max_image_size = 0 # If we're asked to only calculate the maximum image size, we're done. if calc_max_image_size: @@ -3058,10 +3257,16 @@ class Avb(object): image = ImageHandler(image_filename) - if partition_size % image.block_size != 0: - raise AvbError('Partition size of {} is not a multiple of the image ' - 'block size {}.'.format(partition_size, - image.block_size)) + if partition_size > 0: + if partition_size % image.block_size != 0: + raise AvbError('Partition size of {} is not a multiple of the image ' + 'block size {}.'.format(partition_size, + image.block_size)) + else: + if image.image_size % image.block_size != 0: + raise AvbError('File size of {} is not a multiple of the image ' + 'block size {}.'.format(image.image_size, + image.block_size)) # If there's already a footer, truncate the image to its original # size. This way 'avbtool add_hashtree_footer' is idempotent @@ -3088,18 +3293,20 @@ class Avb(object): image.append_raw('\0' * (rounded_image_size - image.image_size)) # If image size exceeds the maximum image size, fail. - if image.image_size > max_image_size: - raise AvbError('Image size of {} exceeds maximum image ' - 'size of {} in order to fit in a partition ' - 'size of {}.'.format(image.image_size, max_image_size, - partition_size)) + if partition_size > 0: + if image.image_size > max_image_size: + raise AvbError('Image size of {} exceeds maximum image ' + 'size of {} in order to fit in a partition ' + 'size of {}.'.format(image.image_size, max_image_size, + partition_size)) if salt: salt = salt.decode('hex') else: - if salt is None: - # If salt is not explicitly specified, choose a hash - # that's the same size as the hash size. + if salt is None and not use_persistent_root_digest: + # If salt is not explicitly specified, choose a hash that's the same + # size as the hash size. Don't populate a random salt if this + # descriptor is being created to use a persistent digest on device. hash_size = digest_size salt = open('/dev/urandom').read(hash_size) else: @@ -3191,8 +3398,9 @@ class Avb(object): # Now insert a DONT_CARE chunk with enough bytes such that the # final Footer block is at the end of partition_size.. - image.append_dont_care(partition_size - image.image_size - - 1*image.block_size) + if partition_size > 0: + image.append_dont_care(partition_size - image.image_size - + 1*image.block_size) # Generate the Footer that tells where the VBMeta footer # is. Also put enough padding in the front of the footer since @@ -3213,7 +3421,7 @@ class Avb(object): def make_atx_certificate(self, output, authority_key_path, subject_key_path, subject_key_version, subject, - is_intermediate_authority, signing_helper, + is_intermediate_authority, usage, signing_helper, signing_helper_with_files): """Implements the 'make_atx_certificate' command. @@ -3235,6 +3443,7 @@ class Avb(object): should be the same Product ID found in the permanent attributes. is_intermediate_authority: True if the certificate is for an intermediate authority. + usage: If not empty, overrides the cert usage with a hash of this value. signing_helper: Program which signs a hash and returns the signature. signing_helper_with_files: Same as signing_helper but uses files instead. """ @@ -3244,9 +3453,10 @@ class Avb(object): hasher = hashlib.sha256() hasher.update(subject) signed_data.extend(hasher.digest()) - usage = 'com.google.android.things.vboot' - if is_intermediate_authority: - usage += '.ca' + if not usage: + usage = 'com.google.android.things.vboot' + if is_intermediate_authority: + usage += '.ca' hasher = hashlib.sha256() hasher.update(usage) signed_data.extend(hasher.digest()) @@ -3322,6 +3532,67 @@ class Avb(object): output.write(intermediate_key_certificate) output.write(product_key_certificate) + def make_atx_unlock_credential(self, output, intermediate_key_certificate, + unlock_key_certificate, challenge_path, + unlock_key_path, signing_helper, + signing_helper_with_files): + """Implements the 'make_atx_unlock_credential' command. + + Android Things unlock credentials can be used to authorize the unlock of AVB + on a device. These credentials are presented to an Android Things bootloader + via the fastboot interface in response to a 16-byte challenge. This method + creates all fields of the credential except the challenge signature field + (which is the last field) and can optionally create the challenge signature + field as well if a challenge and the unlock_key_path is provided. + + Arguments: + output: The credential will be written to this file on success. + intermediate_key_certificate: A certificate file as output by + make_atx_certificate with + is_intermediate_authority set to true. + unlock_key_certificate: A certificate file as output by + make_atx_certificate with + is_intermediate_authority set to false and the + usage set to + 'com.google.android.things.vboot.unlock'. + challenge_path: [optional] A path to the challenge to sign. + unlock_key_path: [optional] A PEM file path with the unlock private key. + signing_helper: Program which signs a hash and returns the signature. + signing_helper_with_files: Same as signing_helper but uses files instead. + + Raises: + AvbError: If an argument is incorrect. + """ + EXPECTED_CERTIFICATE_SIZE = 1620 + EXPECTED_CHALLENGE_SIZE = 16 + if len(intermediate_key_certificate) != EXPECTED_CERTIFICATE_SIZE: + raise AvbError('Invalid intermediate key certificate length.') + if len(unlock_key_certificate) != EXPECTED_CERTIFICATE_SIZE: + raise AvbError('Invalid product key certificate length.') + challenge = bytearray() + if challenge_path: + with open(challenge_path, 'r') as f: + challenge = f.read() + if len(challenge) != EXPECTED_CHALLENGE_SIZE: + raise AvbError('Invalid unlock challenge length.') + output.write(struct.pack(' max_image_size) { + throw IllegalArgumentException("Image size of $originalFileSize exceeds maximum image size " + + "of $max_image_size in order to fit in a partition size of $partition_size.") + } + fis.skip(originalFileSize - 64) try { val footer = Footer(fis) original_image_size = footer.originalImageSize FileOutputStream(File(image_file), true).channel.use { - log.info("truncate $image_file to its original SIZE ${footer.originalImageSize}") + log.info("original image $image_file has AVB footer, " + + "truncate it to original SIZE: ${footer.originalImageSize}") it.truncate(footer.originalImageSize) } } catch (e: IllegalArgumentException) { - log.info("original image doesn't have footer") - original_image_size = File(image_file).length() + log.info("original image $image_file doesn't have AVB footer") + original_image_size = originalFileSize } - val saltByteArray = Helper.fromHexString(salt) + //salt + var saltByteArray = Helper.fromHexString(salt) + if (salt.isBlank()) { + //If salt is not explicitly specified, choose a hash that's the same size as the hash size + val expectedDigestSize = MessageDigest.getInstance(Helper.pyAlg2java(hash_algorithm)).digest().size + FileInputStream(File("/dev/urandom")).use { + val randomSalt = ByteArray(expectedDigestSize) + it.read(randomSalt) + log.warn("salt is empty, using random salt[$expectedDigestSize]: " + Helper.toHexString(randomSalt)) + saltByteArray = randomSalt + } + } else { + log.info("preset salt[${saltByteArray.size}] is valid: $salt") + } + + //hash digest val digest = MessageDigest.getInstance(Helper.pyAlg2java(hash_algorithm)).apply { update(saltByteArray) update(File(image_file).readBytes()) }.digest() - log.info("Digest: " + Helper.toHexString(digest)) + log.info("Digest(salt + file): " + Helper.toHexString(digest)) + //HashDescriptor val hd = HashDescriptor() hd.image_size = File(image_file).length() hd.hash_algorithm = hash_algorithm.toByteArray() @@ -80,6 +111,8 @@ class Avb { if (do_not_use_ab) hd.flags = hd.flags or 1 if (!use_persistent_digest) hd.digest = digest log.info("encoded hash descriptor:" + Hex.encodeHexString(hd.encode())) + + //VBmeta blob val vbmeta_blob = generateVbMetaBlob(common_algorithm, null, arrayOf(hd as Descriptor), @@ -91,7 +124,10 @@ class Avb { 0, inReleaseString) log.debug("vbmeta_blob: " + Helper.toHexString(vbmeta_blob)) + Helper.dumpToFile("hashDescriptor.vbmeta.blob", vbmeta_blob) + log.info("Padding image ...") + // image + padding if (hd.image_size % BLOCK_SIZE != 0L) { val padding_needed = BLOCK_SIZE - (hd.image_size % BLOCK_SIZE) FileOutputStream(image_file, true).use { fos -> @@ -101,17 +137,25 @@ class Avb { } else { log.info("$image_file doesn't need padding") } - val vbmeta_offset = hd.image_size + + // + vbmeta + padding + log.info("Appending vbmeta ...") + val vbmeta_offset = File(image_file).length() val padding_needed = Helper.round_to_multiple(vbmeta_blob.size.toLong(), BLOCK_SIZE) - vbmeta_blob.size val vbmeta_blob_with_padding = Helper.join(vbmeta_blob, Struct("${padding_needed}x").pack(null)) FileOutputStream(image_file, true).use { fos -> fos.write(vbmeta_blob_with_padding) } + + // + DONT_CARE chunk + log.info("Appending DONT_CARE chunk ...") val vbmeta_end_offset = vbmeta_offset + vbmeta_blob_with_padding.size FileOutputStream(image_file, true).use { fos -> fos.write(Struct("${partition_size - vbmeta_end_offset - 1 * BLOCK_SIZE}x").pack(null)) } + // + AvbFooter + padding + log.info("Appending footer ...") val footer = Footer() footer.originalImageSize = original_image_size footer.vbMetaOffset = vbmeta_offset @@ -124,28 +168,41 @@ class Avb { FileOutputStream(image_file, true).use { fos -> fos.write(footerBlobWithPadding) } + + log.info("add_hash_footer($image_file) done ...") } + //avbtool::Avb::_generate_vbmeta_blob() fun generateVbMetaBlob(algorithm_name: String, public_key_metadata_path: String?, descriptors: Array, chain_partitions: String?, inRollbackIndex: Long, inFlags: Long, - props: String?, - kernel_cmdlines: String?, + props: Map?, + kernel_cmdlines: List?, required_libavb_version_minor: Int, inReleaseString: String?): ByteArray { //encoded descriptors var encodedDesc: ByteArray = byteArrayOf() descriptors.forEach { encodedDesc = Helper.join(encodedDesc, it.encode()) } + props?.let { + it.forEach { t, u -> + Helper.join(encodedDesc, PropertyDescriptor(t, u).encode()) + } + } + kernel_cmdlines?.let { + it.forEach { eachCmdline -> + Helper.join(encodedDesc, KernelCmdlineDescriptor(cmdline = eachCmdline).encode()) + } + } //algorithm val alg = Algorithms.get(algorithm_name)!! //encoded pubkey - val encodedKey = Blob.encodePubKey(alg) + val encodedKey = AuxBlob.encodePubKey(alg) //3 - whole aux blob - val auxBlob = Blob.getAuxDataBlob(encodedDesc, encodedKey) + val auxBlob = Blob.getAuxDataBlob(encodedDesc, encodedKey, byteArrayOf()) //1 - whole header blob val headerBlob = Header().apply { @@ -182,7 +239,7 @@ class Avb { }.encode() //2 - auth blob - var authBlob = Blob.getAuthBlob(headerBlob, auxBlob, algorithm_name) + val authBlob = Blob.getAuthBlob(headerBlob, auxBlob, algorithm_name) return Helper.join(headerBlob, authBlob, auxBlob) } @@ -217,15 +274,15 @@ class Avb { val ai = AVBInfo() ai.footer = footer - ai.auxBlob = AVBInfo.AuxBlob() + ai.auxBlob = AuxBlob() ai.header = vbMetaHeader if (vbMetaHeader.public_key_size > 0L) { - ai.auxBlob!!.pubkey = AVBInfo.AuxBlob.PubKeyInfo() + ai.auxBlob!!.pubkey = AuxBlob.PubKeyInfo() ai.auxBlob!!.pubkey!!.offset = vbMetaHeader.public_key_offset ai.auxBlob!!.pubkey!!.size = vbMetaHeader.public_key_size } if (vbMetaHeader.public_key_metadata_size > 0L) { - ai.auxBlob!!.pubkeyMeta = AVBInfo.AuxBlob.PubKeyMetadataInfo() + ai.auxBlob!!.pubkeyMeta = AuxBlob.PubKeyMetadataInfo() ai.auxBlob!!.pubkeyMeta!!.offset = vbMetaHeader.public_key_metadata_offset ai.auxBlob!!.pubkeyMeta!!.size = vbMetaHeader.public_key_metadata_size } @@ -275,7 +332,7 @@ class Avb { fis.read(bb) log.debug("Parsed Auth Signature (of hash): " + Hex.encodeHexString(bb)) - ai.authBlob = AVBInfo.AuthBlob() + ai.authBlob = AuthBlob() ai.authBlob!!.offset = authBlockOffset ai.authBlob!!.size = vbMetaHeader.authentication_data_block_size ai.authBlob!!.hash = Hex.encodeHexString(ba) @@ -321,7 +378,7 @@ class Avb { val alg = Algorithms.get(ai.header!!.algorithm_type.toInt())!! val encodedDesc = ai.auxBlob!!.encodeDescriptors() //encoded pubkey - val encodedKey = Blob.encodePubKey(alg) + val encodedKey = AuxBlob.encodePubKey(alg) //3 - whole aux blob var auxBlob = byteArrayOf() @@ -331,7 +388,7 @@ class Avb { } else { log.warn("Using different key from original vbmeta") } - auxBlob = Blob.getAuxDataBlob(encodedDesc, encodedKey) + auxBlob = Blob.getAuxDataBlob(encodedDesc, encodedKey, byteArrayOf()) } else { log.info("No aux blob") } @@ -381,9 +438,9 @@ class Avb { companion object { private val log = LoggerFactory.getLogger(Avb::class.java) - val AVB_VERSION_MAJOR = 1 - val AVB_VERSION_MINOR = 1 - val AVB_VERSION_SUB = 0 + const val AVB_VERSION_MAJOR = 1 + const val AVB_VERSION_MINOR = 1 + const val AVB_VERSION_SUB = 0 fun getJsonFileName(image_file: String): String { val fileName = File(image_file).name diff --git a/bbootimg/src/main/kotlin/Helper.kt b/bbootimg/src/main/kotlin/Helper.kt index d5a40f7..aeec9ff 100644 --- a/bbootimg/src/main/kotlin/Helper.kt +++ b/bbootimg/src/main/kotlin/Helper.kt @@ -18,20 +18,26 @@ import java.math.RoundingMode import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Paths -import java.security.KeyFactory -import java.security.PrivateKey -import java.security.spec.PKCS8EncodedKeySpec -import java.security.spec.RSAPrivateKeySpec import java.util.zip.GZIPInputStream import java.util.zip.GZIPOutputStream import javax.crypto.Cipher class Helper { companion object { + fun joinWithNulls(vararg source: ByteArray?): ByteArray { + val baos = ByteArrayOutputStream() + for (src in source) { + src?.let { + if (src.isNotEmpty()) baos.write(src) + } + } + return baos.toByteArray() + } + fun join(vararg source: ByteArray): ByteArray { val baos = ByteArrayOutputStream() for (src in source) { - if (source.isNotEmpty()) baos.write(src) + if (src.isNotEmpty()) baos.write(src) } return baos.toByteArray() } @@ -76,7 +82,7 @@ class Helper { while (true) { bytesRead = fis.read(buffer) if (bytesRead <= 0) break - gos.write(buffer, 0, bytesRead); + gos.write(buffer, 0, bytesRead) } gos.finish() log.info("gzip done: $decompressedFile -> $compressedFile") @@ -179,6 +185,7 @@ class Helper { @return: AvbRSAPublicKeyHeader formatted bytearray https://android.googlesource.com/platform/external/avb/+/master/libavb/avb_crypto.h#158 + from avbtool::encode_rsa_key() */ fun encodeRSAkey(key: ByteArray): ByteArray { val rsa = KeyUtil.parsePemPrivateKey(ByteArrayInputStream(key)) @@ -253,6 +260,14 @@ class Helper { } } + fun dumpToFile(dumpFile: String, data: ByteArray) { + log.info("Dumping data to $dumpFile ...") + FileOutputStream(dumpFile, false).use { fos -> + fos.write(data) + } + log.info("Dumping data to $dumpFile done") + } + private val log = LoggerFactory.getLogger("Helper") } } diff --git a/bbootimg/src/main/kotlin/ImgArgs.kt b/bbootimg/src/main/kotlin/ImgArgs.kt deleted file mode 100644 index 22eaf44..0000000 --- a/bbootimg/src/main/kotlin/ImgArgs.kt +++ /dev/null @@ -1,214 +0,0 @@ -package cfig - -import org.apache.commons.exec.CommandLine -import java.util.* - -data class ImgArgs( - //file input - var kernel: String = UnifiedConfig.workDir + "kernel", - var ramdisk: String? = UnifiedConfig.workDir + "ramdisk.img.gz", - var second: String? = UnifiedConfig.workDir + "second", - var dtbo: String? = UnifiedConfig.workDir + "dtbo", - //file output - var output: String = "boot.img", - var cfg: String = UnifiedConfig.workDir + "bootimg.json", - - //load address - internal var base: Long = 0, - internal var kernelOffset: Long = 0, - var ramdiskOffset: Long = 0, - var secondOffset: Long = 0, - var tagsOffset: Long = 0, - var dtboOffset: Long = 0, - - var board: String = "", - var cmdline: String = "", - var osVersion: String? = null, - var osPatchLevel: String? = null, - var headerVersion: Int = 0, - var pageSize: Int = 0, - var id: Boolean = true, - //internal - var mkbootimg: String = "../src/mkbootimg/mkbootimg", - - //signature - var verifyType: VerifyType = VerifyType.VERIFY -) { - enum class VerifyType { - VERIFY, - AVB - } - - fun toCommandList(): List { - val ret = ArrayList() - ret.add(mkbootimg) - ret.add("--header_version") - ret.add(headerVersion.toString()) - ret.add("--base") - ret.add("0x" + java.lang.Long.toHexString(base)) - ret.add("--kernel") - ret.add(kernel) - ret.add("--kernel_offset") - ret.add("0x" + java.lang.Long.toHexString(kernelOffset)) - ramdisk?.let { - ret.add("--ramdisk") - ret.add(it) - } - ret.add("--ramdisk_offset") - ret.add("0x" + java.lang.Long.toHexString(ramdiskOffset)) - second?.let { - ret.add("--second") - ret.add(it) - } - ret.add("--second_offset") - ret.add("0x" + java.lang.Long.toHexString(secondOffset)) - if (!board.isBlank()) { - ret.add("--board") - ret.add(board) - } - if (headerVersion > 0) { - dtbo?.let { dtbo -> - ret.add("--recovery_dtbo") - ret.add(dtbo) - } - ret.add("--recovery_dtbo_offset") - ret.add("0x" + java.lang.Long.toHexString(dtboOffset)) - } - ret.add("--pagesize") - ret.add(Integer.toString(pageSize)) - ret.add("--cmdline") - ret.add(cmdline) - if (!osVersion.isNullOrBlank()) { - ret.add("--os_version") - ret.add(osVersion!!) - } - if (!osPatchLevel.isNullOrBlank()) { - ret.add("--os_patch_level") - ret.add(osPatchLevel!!) - } - ret.add("--tags_offset") - ret.add("0x" + java.lang.Long.toHexString(tagsOffset)) - if (id) { - ret.add("--id") - } - ret.add("--output") - ret.add(output + ".google") - - return ret - } - - fun toCommandString(): String { - val ret = StringBuilder() - ret.append(mkbootimg) - ret.append(" --header_version ") - ret.append(headerVersion.toString()) - ret.append(" --base ") - ret.append("0x" + java.lang.Long.toHexString(base)) - ret.append(" --kernel ") - ret.append(kernel) - ret.append(" --kernel_offset ") - ret.append("0x" + java.lang.Long.toHexString(kernelOffset)) - ramdisk?.let { - ret.append(" --ramdisk ") - ret.append(it) - } - ret.append(" --ramdisk_offset ") - ret.append("0x" + java.lang.Long.toHexString(ramdiskOffset)) - second?.let { - ret.append(" --second ") - ret.append(it) - } - ret.append(" --second_offset ") - ret.append("0x" + java.lang.Long.toHexString(secondOffset)) - if (!board.isBlank()) { - ret.append(" --board ") - ret.append(board) - } - if (headerVersion > 0) { - dtbo?.let { dtbo -> - ret.append(" --recovery_dtbo ") - ret.append(dtbo) - } - ret.append(" --recovery_dtbo_offset ") - ret.append("0x" + java.lang.Long.toHexString(dtboOffset)) - } - ret.append(" --pagesize ") - ret.append(Integer.toString(pageSize)) - ret.append(" --cmdline ") - ret.append("\"" + cmdline + "\"") - if (!osVersion.isNullOrBlank()) { - ret.append(" --os_version ") - ret.append(osVersion) - } - if (!osPatchLevel.isNullOrBlank()) { - ret.append(" --os_patch_level ") - ret.append(osPatchLevel) - } - ret.append(" --tags_offset ") - ret.append("0x" + java.lang.Long.toHexString(tagsOffset)) - if (id) { - ret.append(" --id ") - } - ret.append(" --output ") - ret.append(output + ".google") - - return ret.toString() - } - - fun toCommandLine(): CommandLine { - val ret = CommandLine(mkbootimg) - ret.addArgument(" --header_version ") - ret.addArgument(headerVersion.toString()) - ret.addArgument(" --base ") - ret.addArgument("0x" + java.lang.Long.toHexString(base)) - ret.addArgument(" --kernel ") - ret.addArgument(kernel) - ret.addArgument(" --kernel_offset ") - ret.addArgument("0x" + java.lang.Long.toHexString(kernelOffset)) - ramdisk?.let { - ret.addArgument(" --ramdisk ") - ret.addArgument(it) - } - ret.addArgument(" --ramdisk_offset ") - ret.addArgument("0x" + java.lang.Long.toHexString(ramdiskOffset)) - second?.let { - ret.addArgument(" --second ") - ret.addArgument(it) - } - ret.addArgument(" --second_offset ") - ret.addArgument("0x" + java.lang.Long.toHexString(secondOffset)) - if (!board.isBlank()) { - ret.addArgument(" --board ") - ret.addArgument(board) - } - if (headerVersion > 0) { - dtbo?.let { dtbo -> - ret.addArgument(" --recovery_dtbo ") - ret.addArgument(dtbo) - } - ret.addArgument(" --recovery_dtbo_offset ") - ret.addArgument("0x" + java.lang.Long.toHexString(dtboOffset)) - } - ret.addArgument(" --pagesize ") - ret.addArgument(Integer.toString(pageSize)) - ret.addArgument(" --cmdline ") - ret.addArgument(cmdline, false) - if (!osVersion.isNullOrBlank()) { - ret.addArgument(" --os_version ") - ret.addArgument(osVersion) - } - if (!osPatchLevel.isNullOrBlank()) { - ret.addArgument(" --os_patch_level ") - ret.addArgument(osPatchLevel) - } - ret.addArgument(" --tags_offset ") - ret.addArgument("0x" + java.lang.Long.toHexString(tagsOffset)) - if (id) { - ret.addArgument(" --id ") - } - ret.addArgument(" --output ") - ret.addArgument(output + ".google") - - return ret - } -} diff --git a/bbootimg/src/main/kotlin/ImgInfo.kt b/bbootimg/src/main/kotlin/ImgInfo.kt index 05dd6c1..3128b4b 100644 --- a/bbootimg/src/main/kotlin/ImgInfo.kt +++ b/bbootimg/src/main/kotlin/ImgInfo.kt @@ -35,4 +35,4 @@ data class ImgInfo( var verity_pk8: String = "security/verity.pk8", var verity_pem: String = "security/verity.x509.pem", var jarPath: String = "boot_signer/build/libs/boot_signer.jar") -} \ No newline at end of file +} diff --git a/bbootimg/src/main/kotlin/Packer.kt b/bbootimg/src/main/kotlin/Packer.kt index 0d1fcab..f55093e 100644 --- a/bbootimg/src/main/kotlin/Packer.kt +++ b/bbootimg/src/main/kotlin/Packer.kt @@ -1,16 +1,16 @@ package cfig +import cfig.bootimg.BootImgInfo import com.fasterxml.jackson.databind.ObjectMapper import org.apache.commons.exec.CommandLine import org.apache.commons.exec.DefaultExecutor import org.apache.commons.exec.PumpStreamHandler +import org.junit.Assert.assertTrue import org.slf4j.LoggerFactory import java.io.* import java.nio.ByteBuffer import java.nio.ByteOrder import java.security.MessageDigest -import java.util.regex.Pattern -import org.junit.Assert.* class Packer { private val log = LoggerFactory.getLogger("Packer") @@ -69,148 +69,34 @@ class Packer { inBF.put(ByteArray(pad)) } - private fun writeData(inArgs: ImgArgs) { + private fun writeData(info2: BootImgInfo, outputFile: String) { log.info("Writing data ...") + val param = ParamConfig() + val bf = ByteBuffer.allocate(1024 * 1024 * 64)//assume total SIZE small than 64MB bf.order(ByteOrder.LITTLE_ENDIAN) - writePaddedFile(bf, inArgs.kernel, inArgs.pageSize) - inArgs.ramdisk?.let { ramdisk -> - writePaddedFile(bf, ramdisk, inArgs.pageSize) - } - inArgs.second?.let { second -> - writePaddedFile(bf, second, inArgs.pageSize) - } - inArgs.dtbo?.let { dtbo -> - writePaddedFile(bf, dtbo, inArgs.pageSize) - } - //write - FileOutputStream(inArgs.output + ".clear", true).use { fos -> - fos.write(bf.array(), 0, bf.position()) + writePaddedFile(bf, param.kernel, info2.pageSize) + if (info2.ramdiskLength > 0) { + writePaddedFile(bf, param.ramdisk!!, info2.pageSize) } - } - - @Throws(IllegalArgumentException::class) - private fun packOsVersion(x: String?): Int { - if (x.isNullOrBlank()) return 0 - val pattern = Pattern.compile("^(\\d{1,3})(?:\\.(\\d{1,3})(?:\\.(\\d{1,3}))?)?") - val m = pattern.matcher(x) - if (m.find()) { - val a = Integer.decode(m.group(1)) - var b = 0 - var c = 0 - if (m.groupCount() >= 2) { - b = Integer.decode(m.group(2)) - } - if (m.groupCount() == 3) { - c = Integer.decode(m.group(3)) - } - assertTrue(a < 128) - assertTrue(b < 128) - assertTrue(c < 128) - return (a shl 14) or (b shl 7) or c - } else { - throw IllegalArgumentException("invalid os_version") + if (info2.secondBootloaderLength > 0) { + writePaddedFile(bf, param.second!!, info2.pageSize) } - } - - private fun parseOsPatchLevel(x: String?): Int { - if (x.isNullOrBlank()) return 0 - val ret: Int - val pattern = Pattern.compile("^(\\d{4})-(\\d{2})-(\\d{2})") - val matcher = pattern.matcher(x) - if (matcher.find()) { - val y = Integer.parseInt(matcher.group(1), 10) - 2000 - val m = Integer.parseInt(matcher.group(2), 10) - // 7 bits allocated for the year, 4 bits for the month - assertTrue(y in 0..127) - assertTrue(m in 1..12) - ret = (y shl 4) or m - } else { - throw IllegalArgumentException("invalid os_patch_level") + if (info2.recoveryDtboLength > 0) { + writePaddedFile(bf, param.dtbo!!, info2.pageSize) } - - return ret - } - - private fun writeHeader(inArgs: ImgArgs): ByteArray { - log.info("Writing header ...") - val bf = ByteBuffer.allocate(1024 * 32) - bf.order(ByteOrder.LITTLE_ENDIAN) - - //header start - bf.put("ANDROID!".toByteArray()) - bf.putInt(File(inArgs.kernel).length().toInt()) - bf.putInt((inArgs.base + inArgs.kernelOffset).toInt()) - - if (null == inArgs.ramdisk) { - bf.putInt(0) - } else { - bf.putInt(File(inArgs.ramdisk).length().toInt()) + if (info2.dtbLength > 0) { + writePaddedFile(bf, param.dtb!!, info2.pageSize) } - - bf.putInt((inArgs.base + inArgs.ramdiskOffset).toInt()) - - if (null == inArgs.second) { - bf.putInt(0) - } else { - bf.putInt(File(inArgs.second).length().toInt()) - } - - bf.putInt((inArgs.base + inArgs.secondOffset).toInt()) - bf.putInt((inArgs.base + inArgs.tagsOffset).toInt()) - bf.putInt(inArgs.pageSize) - bf.putInt(inArgs.headerVersion) - bf.putInt((packOsVersion(inArgs.osVersion) shl 11) or parseOsPatchLevel(inArgs.osPatchLevel)) - - if (inArgs.board.isBlank()) { - bf.put(ByteArray(16)) - } else { - bf.put(inArgs.board.toByteArray()) - bf.put(ByteArray(16 - inArgs.board.length)) - } - - bf.put(inArgs.cmdline.substring(0, minOf(512, inArgs.cmdline.length)).toByteArray()) - bf.put(ByteArray(512 - minOf(512, inArgs.cmdline.length))) - - //hash - val imageId = if (inArgs.headerVersion > 0) { - hashFileAndSize(inArgs.kernel, inArgs.ramdisk, inArgs.second, inArgs.dtbo) - } else { - hashFileAndSize(inArgs.kernel, inArgs.ramdisk, inArgs.second) - } - bf.put(imageId) - bf.put(ByteArray(32 - imageId.size)) - - if (inArgs.cmdline.length > 512) { - bf.put(inArgs.cmdline.substring(512).toByteArray()) - bf.put(ByteArray(1024 + 512 - inArgs.cmdline.length)) - } else { - bf.put(ByteArray(1024)) - } - - if (inArgs.headerVersion > 0) { - if (inArgs.dtbo == null) { - bf.putInt(0) - } else { - bf.putInt(File(inArgs.dtbo).length().toInt()) - } - bf.putLong(inArgs.dtboOffset) - bf.putInt(1648) - } - - //padding - padFile(bf, inArgs.pageSize) - //write - FileOutputStream(inArgs.output + ".clear", false).use { fos -> + FileOutputStream(outputFile + ".clear", true).use { fos -> fos.write(bf.array(), 0, bf.position()) } - - return imageId } - fun packRootfs(args: ImgArgs, mkbootfs: String) { + fun packRootfs(mkbootfs: String) { + val param = ParamConfig() log.info("Packing rootfs ${UnifiedConfig.workDir}root ...") val outputStream = ByteArrayOutputStream() val exec = DefaultExecutor() @@ -218,8 +104,8 @@ class Packer { val cmdline = "$mkbootfs ${UnifiedConfig.workDir}root" log.info(cmdline) exec.execute(CommandLine.parse(cmdline)) - Helper.gnuZipFile2(args.ramdisk!!, ByteArrayInputStream(outputStream.toByteArray())) - log.info("${args.ramdisk} is ready") + Helper.gnuZipFile2(param.ramdisk!!, ByteArrayInputStream(outputStream.toByteArray())) + log.info("${param.ramdisk} is ready") } private fun File.deleleIfExists() { @@ -232,39 +118,44 @@ class Packer { } } - fun pack(mkbootimgBin: String, mkbootfsBin: String) { - log.info("Loading config from ${workDir}bootimg.json") - val cfg = ObjectMapper().readValue(File(workDir + "bootimg.json"), UnifiedConfig::class.java) - val readBack = cfg.toArgs() - val args = readBack[0] as ImgArgs - val info = readBack[1] as ImgInfo - args.mkbootimg = mkbootimgBin - log.debug(args.toString()) - log.debug(info.toString()) + fun pack(mkbootfsBin: String) { + val param = ParamConfig() + log.info("Loading config from ${param.cfg}") + val cfg = ObjectMapper().readValue(File(param.cfg), UnifiedConfig::class.java) + val info2 = cfg.toBootImgInfo() //clean - File(args.output + ".google").deleleIfExists() - File(args.output + ".clear").deleleIfExists() - File(args.output + ".signed").deleleIfExists() - File(args.output + ".signed2").deleleIfExists() + File(cfg.info.output + ".google").deleleIfExists() + File(cfg.info.output + ".clear").deleleIfExists() + File(cfg.info.output + ".signed").deleleIfExists() + File(cfg.info.output + ".signed2").deleleIfExists() File("${UnifiedConfig.workDir}ramdisk.img").deleleIfExists() - args.ramdisk?.let { - if (File(it).exists() && !File(UnifiedConfig.workDir + "root").exists()) { + if (info2.ramdiskLength > 0) { + if (File(param.ramdisk).exists() && !File(UnifiedConfig.workDir + "root").exists()) { //do nothing if we have ramdisk.img.gz but no /root - log.warn("Use prebuilt ramdisk file: $it") + log.warn("Use prebuilt ramdisk file: ${param.ramdisk}") } else { - File(it).deleleIfExists() - packRootfs(args, mkbootfsBin) + File(param.ramdisk).deleleIfExists() + packRootfs(mkbootfsBin) } } - writeHeader(args) - writeData(args) + val encodedHeader = info2.encode() + //write + FileOutputStream(cfg.info.output + ".clear", false).use { fos -> + fos.write(encodedHeader) + fos.write(ByteArray(info2.pageSize - encodedHeader.size)) + } + writeData(info2, cfg.info.output) + + val googleCmd = info2.toCommandLine().apply { + addArgument(cfg.info.output + ".google") + } + DefaultExecutor().execute(googleCmd) - DefaultExecutor().execute(args.toCommandLine()) - val ourHash = hashFileAndSize(args.output + ".clear") - val googleHash = hashFileAndSize(args.output + ".google") + val ourHash = hashFileAndSize(cfg.info.output + ".clear") + val googleHash = hashFileAndSize(cfg.info.output + ".google") log.info("ours hash ${Helper.toHexString(ourHash)}, google's hash ${Helper.toHexString(googleHash)}") if (ourHash.contentEquals(googleHash)) { log.info("Hash verification passed: ${Helper.toHexString(ourHash)}") diff --git a/bbootimg/src/main/kotlin/ParamConfig.kt b/bbootimg/src/main/kotlin/ParamConfig.kt new file mode 100644 index 0000000..d6a9fc8 --- /dev/null +++ b/bbootimg/src/main/kotlin/ParamConfig.kt @@ -0,0 +1,11 @@ +package cfig + +data class ParamConfig( + //file input + var kernel: String = UnifiedConfig.workDir + "kernel", + var ramdisk: String? = UnifiedConfig.workDir + "ramdisk.img.gz", + var second: String? = UnifiedConfig.workDir + "second", + var dtbo: String? = UnifiedConfig.workDir + "recoveryDtbo", + var dtb: String? = UnifiedConfig.workDir + "dtb", + var cfg: String = UnifiedConfig.workDir + "bootimg.json", + val mkbootimg: String = "./src/mkbootimg/mkbootimg") diff --git a/bbootimg/src/main/kotlin/Parser.kt b/bbootimg/src/main/kotlin/Parser.kt index 538b360..f629f2f 100644 --- a/bbootimg/src/main/kotlin/Parser.kt +++ b/bbootimg/src/main/kotlin/Parser.kt @@ -1,231 +1,131 @@ package cfig +import cfig.bootimg.BootImgInfo import com.fasterxml.jackson.databind.ObjectMapper import org.apache.commons.exec.CommandLine import org.apache.commons.exec.DefaultExecutor +import org.junit.Assert.assertTrue import org.slf4j.LoggerFactory import java.io.File import java.io.FileInputStream import java.io.InputStream -import java.lang.IllegalStateException import java.nio.ByteBuffer import java.nio.ByteOrder -import org.junit.Assert.* -import org.apache.commons.exec.PumpStreamHandler -import java.io.ByteArrayOutputStream -import java.util.regex.Pattern class Parser { - private val workDir = UnifiedConfig.workDir - - private fun parseOsVersion(x: Int): String { - val a = x shr 14 - val b = x - (a shl 14) shr 7 - val c = x and 0x7f - - return String.format("%d.%d.%d", a, b, c) - } - - private fun parseOsPatchLevel(x: Int): String { - var y = x shr 4 - val m = x and 0xf - y += 2000 - - return String.format("%d-%02d-%02d", y, m, 0) - } - - private fun getHeaderSize(pageSize: Int): Int { - val pad = (pageSize - (1648 and (pageSize - 1))) and (pageSize - 1) - return pad + 1648 - } - - private fun getPaddingSize(position: Int, pageSize: Int): Int { - return (pageSize - (position and pageSize - 1)) and (pageSize - 1) - } - - private fun parseHeader(args: ImgArgs, info: ImgInfo) { - FileInputStream(args.output).use { iS -> - assertTrue(readBytes(iS, 8).contentEquals("ANDROID!".toByteArray())) - info.kernelLength = readInt(iS) - args.kernelOffset = readUnsignedAsLong(iS) - info.ramdiskLength = readInt(iS) - args.ramdiskOffset = readUnsignedAsLong(iS) - info.secondBootloaderLength = readInt(iS) - args.secondOffset = readUnsignedAsLong(iS) - args.tagsOffset = readUnsignedAsLong(iS) - args.pageSize = readInt(iS) - args.headerVersion = readInt(iS) - - val osNPatch = readInt(iS) - if (0 != osNPatch) { //treated as 'reserved' in this boot image - args.osVersion = parseOsVersion(osNPatch shr 11) - args.osPatchLevel = parseOsPatchLevel(osNPatch and 0x7ff) - } - - args.board = Helper.toCString(readBytes(iS, 16)) - if (args.board.isBlank()) { - args.board = "" - } - - val cmd1 = Helper.toCString(readBytes(iS, 512)) - info.hash = readBytes(iS, 32) //hash - val cmd2 = Helper.toCString(readBytes(iS, 1024)) - args.cmdline = cmd1 + cmd2 - - info.recoveryDtboLength = readInt(iS) - args.dtboOffset = readLong(iS) - info.headerSize = readInt(iS) - - //calc subimg positions - info.kernelPosition = getHeaderSize(args.pageSize) - info.ramdiskPosition = info.kernelPosition + info.kernelLength + getPaddingSize(info.kernelLength, args.pageSize) - info.secondBootloaderPosition = info.ramdiskPosition + info.ramdiskLength + getPaddingSize(info.ramdiskLength, args.pageSize) - info.recoveryDtboPosition = info.secondBootloaderPosition + info.secondBootloaderLength + getPaddingSize(info.secondBootloaderLength, args.pageSize) - - //adjust args - if (args.kernelOffset > Int.MAX_VALUE - && args.ramdiskOffset > Int.MAX_VALUE - && args.secondOffset > Int.MAX_VALUE - && args.dtboOffset > Int.MAX_VALUE) { - args.base = Int.MAX_VALUE + 1L - args.kernelOffset -= args.base - args.ramdiskOffset -= args.base - args.secondOffset -= args.base - args.tagsOffset -= args.base - args.dtboOffset -= args.base - } - - if (info.ramdiskLength == 0) args.ramdisk = null - if (info.kernelLength == 0) throw IllegalStateException("boot image has no kernel") - if (info.secondBootloaderLength == 0) args.second = null - if (info.recoveryDtboLength == 0) args.dtbo = null - } - }//resource-closable - - private fun verifiedWithAVB(args: ImgArgs): Boolean { + private fun verifiedWithAVB(fileName: String): Boolean { val expectedBf = "AVBf".toByteArray() - FileInputStream(args.output).use { fis -> - fis.skip(File(args.output).length() - 64) + FileInputStream(fileName).use { fis -> + fis.skip(File(fileName).length() - 64) val bf = ByteArray(4) fis.read(bf) return bf.contentEquals(expectedBf) } } - private fun verifyAVBIntegrity(args: ImgArgs, avbtool: String) { - val cmdline = "$avbtool verify_image --image ${args.output}" - log.info(cmdline) - DefaultExecutor().execute(CommandLine.parse(cmdline)) - } - - private fun parseAVBInfo(args: ImgArgs, info: ImgInfo, avbtool: String) { - val outputStream = ByteArrayOutputStream() - val exec = DefaultExecutor() - exec.streamHandler = PumpStreamHandler(outputStream) - val cmdline = "$avbtool info_image --image ${args.output}" - log.info(cmdline) - exec.execute(CommandLine.parse(cmdline)) - val lines = outputStream.toString().split("\n") - lines.forEach { - val m = Pattern.compile("^Original image size:\\s+(\\d+)\\s*bytes").matcher(it) - if (m.find()) { - (info.signature as ImgInfo.AvbSignature).originalImageSize = Integer.parseInt(m.group(1)) - } - - val m2 = Pattern.compile("^Image size:\\s+(\\d+)\\s*bytes").matcher(it) - if (m2.find()) { - (info.signature as ImgInfo.AvbSignature).imageSize = Integer.parseInt(m2.group(1)) - } - - val m3 = Pattern.compile("^\\s*Partition Name:\\s+(\\S+)$").matcher(it) - if (m3.find()) { - (info.signature as ImgInfo.AvbSignature).partName = m3.group(1) - - } - - val m4 = Pattern.compile("^\\s*Salt:\\s+(\\S+)$").matcher(it) - if (m4.find()) { - (info.signature as ImgInfo.AvbSignature).salt = m4.group(1) - - } - - val m5 = Pattern.compile("^\\s*Algorithm:\\s+(\\S+)$").matcher(it) - if (m5.find()) { - (info.signature as ImgInfo.AvbSignature).algorithm = m5.group(1) - } - - val m6 = Pattern.compile("^\\s*Hash Algorithm:\\s+(\\S+)$").matcher(it) - if (m6.find()) { - (info.signature as ImgInfo.AvbSignature).hashAlgorithm = m6.group(1) - } - - log.debug("[" + it + "]") - } - assertNotNull((info.signature as ImgInfo.AvbSignature).imageSize) - assertNotNull((info.signature as ImgInfo.AvbSignature).originalImageSize) - assertTrue(!(info.signature as ImgInfo.AvbSignature).partName.isNullOrBlank()) - assertTrue(!(info.signature as ImgInfo.AvbSignature).salt.isNullOrBlank()) - } - - private fun unpackRamdisk(imgArgs: ImgArgs) { + private fun unpackRamdisk(workDir: String, ramdiskGz: String) { val exe = DefaultExecutor() exe.workingDirectory = File(workDir + "root") if (exe.workingDirectory.exists()) exe.workingDirectory.deleteRecursively() exe.workingDirectory.mkdirs() - val ramdiskFile = File(imgArgs.ramdisk!!.removeSuffix(".gz")) + val ramdiskFile = File(ramdiskGz.removeSuffix(".gz")) exe.execute(CommandLine.parse("cpio -i -m -F " + ramdiskFile.canonicalPath)) - log.info("extract ramdisk done: $ramdiskFile -> ${exe.workingDirectory.path}") + log.info(" ramdisk extracted : $ramdiskFile -> ${exe.workingDirectory.path}") } - fun parseAndExtract(fileName: String?, avbtool: String) { - val imgArgs = ImgArgs(output = fileName ?: "boot.img") - val imgInfo = ImgInfo() - if (!fileName.isNullOrBlank()) { - imgArgs.output = fileName!! + fun parseBootImgHeader(fileName: String, avbtool: String): BootImgInfo { + val info2 = BootImgInfo(FileInputStream(fileName)) + val param = ParamConfig() + if (verifiedWithAVB(fileName)) { + info2.signatureType = BootImgInfo.VerifyType.AVB + verifyAVBIntegrity(fileName, avbtool) + } else { + info2.signatureType = BootImgInfo.VerifyType.VERIFY } + info2.imageSize = File(fileName).length() - //parse header - parseHeader(imgArgs, imgInfo) + val cfg = UnifiedConfig.fromBootImgInfo(info2).apply { + info.output = File(fileName).name + } - //parse signature - if (verifiedWithAVB(imgArgs)) { - imgArgs.verifyType = ImgArgs.VerifyType.AVB - imgInfo.signature = ImgInfo.AvbSignature() - verifyAVBIntegrity(imgArgs, avbtool) - parseAVBInfo(imgArgs, imgInfo, avbtool) + ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(param.cfg), cfg) + log.info("image info written to ${param.cfg}") + + return info2 + } + + fun extractBootImg(fileName: String, info2: BootImgInfo) { + val param = ParamConfig() + if (info2.kernelLength > 0) { + Helper.extractFile(fileName, + param.kernel, + info2.kernelPosition.toLong(), + info2.kernelLength.toInt()) + log.info(" kernel dumped to: ${param.kernel}, size=${info2.kernelLength / 1024.0 / 1024.0}MB") } else { - imgArgs.verifyType = ImgArgs.VerifyType.VERIFY - imgInfo.signature = ImgInfo.VeritySignature() + throw RuntimeException("bad boot image: no kernel found") } - log.info(imgArgs.toString()) - log.info(imgInfo.toString()) + if (info2.ramdiskLength > 0) { + Helper.extractFile(fileName, + param.ramdisk!!, + info2.ramdiskPosition.toLong(), + info2.ramdiskLength.toInt()) + log.info("ramdisk dumped to: ${param.ramdisk}") + Helper.unGnuzipFile(param.ramdisk!!, param.ramdisk!!.removeSuffix(".gz")) + unpackRamdisk(UnifiedConfig.workDir, param.ramdisk!!.removeSuffix(".gz")) + } else { + log.info("no ramdisk found") + } - Helper.extractFile(imgArgs.output, imgArgs.kernel, imgInfo.kernelPosition.toLong(), imgInfo.kernelLength) - log.info("kernel dumped to ${imgArgs.kernel}") - imgArgs.ramdisk?.let { ramdisk -> - log.info("ramdisk dumped to ${imgArgs.ramdisk}") - Helper.extractFile(imgArgs.output, ramdisk, imgInfo.ramdiskPosition.toLong(), imgInfo.ramdiskLength) - Helper.unGnuzipFile(ramdisk, workDir + "ramdisk.img") - unpackRamdisk(imgArgs) + if (info2.secondBootloaderLength > 0) { + Helper.extractFile(fileName, + param.second!!, + info2.secondBootloaderPosition.toLong(), + info2.secondBootloaderLength.toInt()) + log.info("second bootloader dumped to ${param.second}") + } else { + log.info("no second bootloader found") } - imgArgs.second?.let { second -> - Helper.extractFile(imgArgs.output, second, imgInfo.secondBootloaderPosition.toLong(), imgInfo.secondBootloaderLength) - log.info("second bootloader dumped to ${imgArgs.second}") + + if (info2.recoveryDtboLength > 0) { + Helper.extractFile(fileName, + param.dtbo!!, + info2.recoveryDtboPosition.toLong(), + info2.recoveryDtboLength.toInt()) + log.info("dtbo dumped to ${param.dtbo}") + } else { + if (info2.headerVersion > 0) { + log.info("no recovery dtbo found") + } else { + log.debug("no recovery dtbo for header v0") + } } - imgArgs.dtbo?.let { dtbo -> - Helper.extractFile(imgArgs.output, dtbo, imgInfo.recoveryDtboPosition.toLong(), imgInfo.recoveryDtboLength) - log.info("dtbo dumped to ${imgArgs.dtbo}") + + if (info2.dtbLength > 0) { + Helper.extractFile(fileName, + param.dtb!!, + info2.dtbPosition.toLong(), + info2.dtbLength.toInt()) + log.info("dtb dumped to ${param.dtb}") + } else { + if (info2.headerVersion > 1) { + log.info("no dtb found") + } else { + log.debug("no dtb for header v0") + } } - val cfg = UnifiedConfig.fromArgs(imgArgs, imgInfo) - log.debug(ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(cfg)) - ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(imgArgs.cfg), cfg) - log.info("image info written to ${imgArgs.cfg}") } companion object { private val log = LoggerFactory.getLogger("Parser")!! + fun verifyAVBIntegrity(fileName: String, avbtool: String) { + val cmdline = "$avbtool verify_image --image $fileName" + log.info(cmdline) + DefaultExecutor().execute(CommandLine.parse(cmdline)) + } + fun readShort(iS: InputStream): Short { val bf = ByteBuffer.allocate(128) bf.order(ByteOrder.LITTLE_ENDIAN) diff --git a/bbootimg/src/main/kotlin/R.kt b/bbootimg/src/main/kotlin/R.kt index 8078595..a01b0b1 100755 --- a/bbootimg/src/main/kotlin/R.kt +++ b/bbootimg/src/main/kotlin/R.kt @@ -1,7 +1,6 @@ package cfig -import avb.AVBInfo -import com.fasterxml.jackson.databind.ObjectMapper +import cfig.bootimg.BootImgInfo import org.slf4j.LoggerFactory import java.io.File @@ -27,41 +26,41 @@ fun main(args: Array) { "unpack" -> { if (File(UnifiedConfig.workDir).exists()) File(UnifiedConfig.workDir).deleteRecursively() File(UnifiedConfig.workDir).mkdirs() - Parser().parseAndExtract(fileName = args[1], avbtool = args[3]) - - if (UnifiedConfig.readBack()[2] is ImgInfo.AvbSignature) { + val info = Parser().parseBootImgHeader(fileName = args[1], avbtool = args[3]) + if (info.signatureType == BootImgInfo.VerifyType.AVB) { log.info("continue to analyze vbmeta info in " + args[1]) Avb().parseVbMeta(args[1]) if (File("vbmeta.img").exists()) { Avb().parseVbMeta("vbmeta.img") } } + Parser().extractBootImg(fileName = args[1], info2 = info) } "pack" -> { - Packer().pack(mkbootimgBin = args[2], mkbootfsBin = args[5]) + Packer().pack(mkbootfsBin = args[5]) } "sign" -> { Signer.sign(avbtool = args[3], bootSigner = args[4]) - val readBack = UnifiedConfig.readBack() - if ((readBack[0] as ImgArgs).verifyType == ImgArgs.VerifyType.AVB) { + val readBack2 = UnifiedConfig.readBack2() + if (readBack2.signatureType == BootImgInfo.VerifyType.AVB) { if (File("vbmeta.img").exists()) { - val sig = readBack[2] as ImgInfo.AvbSignature - val newBootImgInfo = Avb().parseVbMeta(args[1] + ".signed") - val hashDesc = newBootImgInfo.auxBlob!!.hashDescriptors[0] - val origVbMeta = ObjectMapper().readValue(File(Avb.getJsonFileName("vbmeta.img")), - AVBInfo::class.java) - for (i in 0..(origVbMeta.auxBlob!!.hashDescriptors.size - 1)) { - if (origVbMeta.auxBlob!!.hashDescriptors[i].partition_name == sig.partName) { - val seq = origVbMeta.auxBlob!!.hashDescriptors[i].sequence - origVbMeta.auxBlob!!.hashDescriptors[i] = hashDesc - origVbMeta.auxBlob!!.hashDescriptors[i].sequence = seq - } - } - ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(Avb.getJsonFileName("vbmeta.img")), origVbMeta) - log.info("vbmeta info updated") - Avb().packVbMetaWithPadding() +// val sig = readBack[2] as ImgInfo.AvbSignature +// val newBootImgInfo = Avb().parseVbMeta(args[1] + ".signed") +// val hashDesc = newBootImgInfo.auxBlob!!.hashDescriptors[0] +// val origVbMeta = ObjectMapper().readValue(File(Avb.getJsonFileName("vbmeta.img")), +// AVBInfo::class.java) +// for (i in 0..(origVbMeta.auxBlob!!.hashDescriptors.size - 1)) { +// if (origVbMeta.auxBlob!!.hashDescriptors[i].partition_name == sig.partName) { +// val seq = origVbMeta.auxBlob!!.hashDescriptors[i].sequence +// origVbMeta.auxBlob!!.hashDescriptors[i] = hashDesc +// origVbMeta.auxBlob!!.hashDescriptors[i].sequence = seq +// } +// } +// ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(Avb.getJsonFileName("vbmeta.img")), origVbMeta) +// log.info("vbmeta.img info updated") +// Avb().packVbMetaWithPadding() } else { - //no vbmeta provided + log.info("no vbmeta.img need to update") } }//end-of-avb }//end-of-sign diff --git a/bbootimg/src/main/kotlin/Signer.kt b/bbootimg/src/main/kotlin/Signer.kt index 22243a4..b9abe4c 100644 --- a/bbootimg/src/main/kotlin/Signer.kt +++ b/bbootimg/src/main/kotlin/Signer.kt @@ -2,6 +2,7 @@ package cfig import avb.AVBInfo import avb.alg.Algorithms +import cfig.bootimg.BootImgInfo import com.fasterxml.jackson.databind.ObjectMapper import org.apache.commons.exec.CommandLine import org.apache.commons.exec.DefaultExecutor @@ -11,68 +12,63 @@ import java.io.File class Signer { companion object { private val log = LoggerFactory.getLogger(Signer::class.java) - private val workDir = UnifiedConfig.workDir fun sign(avbtool: String, bootSigner: String) { - log.info("Loading config from ${workDir}bootimg.json") - val readBack = UnifiedConfig.readBack() - val args = readBack[0] as ImgArgs + log.info("Loading config from ${ParamConfig().cfg}") + val info2 = UnifiedConfig.readBack2() + val cfg = ObjectMapper().readValue(File(ParamConfig().cfg), UnifiedConfig::class.java) - when (args.verifyType) { - ImgArgs.VerifyType.VERIFY -> { + when (info2.signatureType) { + BootImgInfo.VerifyType.VERIFY -> { log.info("Signing with verified-boot 1.0 style") - val sig = readBack[2] as ImgInfo.VeritySignature - DefaultExecutor().execute(CommandLine.parse("java -jar $bootSigner " + - "${sig.path} ${args.output}.clear ${sig.verity_pk8} ${sig.verity_pem} ${args.output}.signed")) + val sig = ImgInfo.VeritySignature() + val bootSignCmd = "java -jar $bootSigner " + + "${sig.path} ${cfg.info.output}.clear " + + "${sig.verity_pk8} ${sig.verity_pem} " + + "${cfg.info.output}.signed" + log.info(bootSignCmd) + DefaultExecutor().execute(CommandLine.parse(bootSignCmd)) } - ImgArgs.VerifyType.AVB -> { + BootImgInfo.VerifyType.AVB -> { log.info("Adding hash_footer with verified-boot 2.0 style") - val sig = readBack[2] as ImgInfo.AvbSignature - val ai = ObjectMapper().readValue(File(Avb.getJsonFileName(args.output)), AVBInfo::class.java) - //val alg = Algorithms.get(ai.header!!.algorithm_type.toInt()) + val ai = ObjectMapper().readValue(File(Avb.getJsonFileName(cfg.info.output)), AVBInfo::class.java) + val alg = Algorithms.get(ai.header!!.algorithm_type.toInt()) + val bootDesc = ai.auxBlob!!.hashDescriptors[0] //our signer - File(args.output + ".clear").copyTo(File(args.output + ".signed")) - Avb().add_hash_footer(args.output + ".signed", - sig.imageSize!!.toLong(), + File(cfg.info.output + ".clear").copyTo(File(cfg.info.output + ".signed")) + Avb().add_hash_footer(cfg.info.output + ".signed", + info2.imageSize.toLong(), false, false, - salt = sig.salt, - hash_algorithm = sig.hashAlgorithm!!, - partition_name = sig.partName!!, + salt = Helper.toHexString(bootDesc.salt), + hash_algorithm = bootDesc.hash_algorithm_str, + partition_name = bootDesc.partition_name, rollback_index = ai.header!!.rollback_index, - common_algorithm = sig.algorithm!!, + common_algorithm = alg!!.name, inReleaseString = ai.header!!.release_string) //original signer - File(args.output + ".clear").copyTo(File(args.output + ".signed2")) - val signKey = Algorithms.get(sig.algorithm!!) + File(cfg.info.output + ".clear").copyTo(File(cfg.info.output + ".signed2")) var cmdlineStr = "$avbtool add_hash_footer " + - "--image ${args.output}.signed2 " + - "--partition_size ${sig.imageSize} " + - "--salt ${sig.salt} " + - "--partition_name ${sig.partName} " + - "--hash_algorithm ${sig.hashAlgorithm} " + - "--algorithm ${sig.algorithm} " - if (signKey!!.defaultKey.isNotBlank()) { - cmdlineStr += "--key ${signKey.defaultKey}" + "--image ${cfg.info.output}.signed2 " + + "--partition_size ${info2.imageSize} " + + "--salt ${Helper.toHexString(bootDesc.salt)} " + + "--partition_name ${bootDesc.partition_name} " + + "--hash_algorithm ${bootDesc.hash_algorithm_str} " + + "--algorithm ${alg.name} " + if (alg.defaultKey.isNotBlank()) { + cmdlineStr += "--key ${alg.defaultKey}" } log.warn(cmdlineStr) val cmdLine = CommandLine.parse(cmdlineStr) cmdLine.addArgument("--internal_release_string") cmdLine.addArgument(ai.header!!.release_string, false) DefaultExecutor().execute(cmdLine) - verifyAVBIntegrity(args, avbtool) + Parser.verifyAVBIntegrity(cfg.info.output, avbtool) } } } - private fun verifyAVBIntegrity(args: ImgArgs, avbtool: String) { - val tgt = args.output + ".signed" - log.info("Verifying AVB: $tgt") - DefaultExecutor().execute(CommandLine.parse("$avbtool verify_image --image $tgt")) - log.info("Verifying image passed: $tgt") - } - fun mapToJson(m: LinkedHashMap<*, *>): String { val sb = StringBuilder() m.forEach { k, v -> diff --git a/bbootimg/src/main/kotlin/UnifiedConfig.kt b/bbootimg/src/main/kotlin/UnifiedConfig.kt index 17173d6..0ecf75e 100644 --- a/bbootimg/src/main/kotlin/UnifiedConfig.kt +++ b/bbootimg/src/main/kotlin/UnifiedConfig.kt @@ -1,5 +1,6 @@ package cfig +import cfig.bootimg.BootImgInfo import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.databind.ObjectMapper import org.slf4j.LoggerFactory @@ -12,7 +13,8 @@ data class UnifiedConfig( var ramdisk: CommArgs? = null, var secondBootloader: CommArgs? = null, var recoveryDtbo: CommArgs? = null, - var signature: Any? = ImgInfo.VeritySignature() + var dtb: CommArgs? = null, + var signature: Any? = null ) { data class CommArgs( var file: String? = null, @@ -31,132 +33,121 @@ data class UnifiedConfig( var cmdline: String = "", var osVersion: String? = null, var osPatchLevel: String? = null, - var hash: String = "", - var verify: ImgArgs.VerifyType = ImgArgs.VerifyType.VERIFY) - - fun toArgs(): Array { - val args = ImgArgs() - val info = ImgInfo() - - args.output = this.info.output - args.kernel = this.kernel.file ?: workDir + "kernel" - args.kernelOffset = this.kernel.loadOffset.removePrefix("0x").toLong(16) - info.kernelPosition = Integer.decode(this.kernel.position) - info.kernelLength = Integer.decode(this.kernel.size) - - if (this.ramdisk == null) { - args.ramdisk = null - } else { - args.ramdisk = this.ramdisk!!.file - args.ramdiskOffset = this.ramdisk!!.loadOffset.removePrefix("0x").toLong(16) - info.ramdiskPosition = Integer.decode(this.ramdisk!!.position) - info.ramdiskLength = Integer.decode(this.ramdisk!!.size) + var hash: ByteArray = byteArrayOf(), + var verify: BootImgInfo.VerifyType = BootImgInfo.VerifyType.VERIFY, + var imageSize: Long = 0) + + fun toBootImgInfo(): BootImgInfo { + val ret = BootImgInfo() + ret.kernelOffset = this.kernel.loadOffset.removePrefix("0x").toLong(16) + ret.kernelLength = Integer.decode(this.kernel.size).toLong() + + ret.kernelOffset = this.kernel.loadOffset.removePrefix("0x").toLong(16) + ret.kernelLength = Integer.decode(this.kernel.size).toLong() + + this.ramdisk?.let { + ret.ramdiskOffset = it.loadOffset.removePrefix("0x").toLong(16) + ret.ramdiskLength = it.size.removePrefix("0x").toLong(16) } this.secondBootloader?.let { - args.second = it.file - args.secondOffset = it.loadOffset.removePrefix("0x").toLong(16) - info.secondBootloaderPosition = Integer.decode(it.position) - info.secondBootloaderLength = Integer.decode(it.size) + ret.secondBootloaderOffset = it.loadOffset.removePrefix("0x").toLong(16) + ret.secondBootloaderLength = it.size.removePrefix("0x").toLong(16) } - if (this.secondBootloader == null) args.second = null this.recoveryDtbo?.let { - args.dtbo = it.file - args.dtboOffset = it.loadOffset.removePrefix("0x").toLong(16) - info.recoveryDtboPosition = Integer.decode(it.position) - info.recoveryDtboLength = Integer.decode(it.size) + ret.recoveryDtboOffset = it.loadOffset.removePrefix("0x").toLong(16) + ret.recoveryDtboLength = it.size.removePrefix("0x").toLong(16) } - if (this.recoveryDtbo == null) args.dtbo = null - - info.headerSize = this.info.headerSize - args.headerVersion = this.info.headerVersion - args.base = this.info.loadBase.removePrefix("0x").toLong(16) - this.info.board?.let { args.board = it } - args.tagsOffset = this.info.tagsOffset.removePrefix("0x").toLong(16) - args.cmdline = this.info.cmdline - args.osVersion = this.info.osVersion - args.osPatchLevel = this.info.osPatchLevel - info.hash = Helper.fromHexString(this.info.hash) - args.pageSize = this.info.pageSize - args.verifyType = this.info.verify - info.signature = this.signature - - return arrayOf(args, info) + + this.dtb?.let { + ret.dtbOffset = it.loadOffset.removePrefix("0x").toLong(16) + ret.dtbLength = it.size.removePrefix("0x").toLong(16) + } + + ret.headerSize = this.info.headerSize.toLong() + ret.headerVersion = this.info.headerVersion + this.info.board?.let { ret.board = it } + ret.tagsOffset = this.info.tagsOffset.removePrefix("0x").toLong(16) + ret.cmdline = this.info.cmdline + ret.osVersion = this.info.osVersion + ret.osPatchLevel = this.info.osPatchLevel + ret.hash = this.info.hash + ret.pageSize = this.info.pageSize + ret.signatureType = this.info.verify + ret.imageSize = this.info.imageSize + + return ret } companion object { const val workDir = "build/unzip_boot/" private val log = LoggerFactory.getLogger(UnifiedConfig::class.java) - fun fromArgs(args: ImgArgs, info: ImgInfo): UnifiedConfig { + + fun fromBootImgInfo(info: BootImgInfo): UnifiedConfig { val ret = UnifiedConfig() - ret.kernel.file = args.kernel - ret.kernel.loadOffset = "0x${java.lang.Long.toHexString(args.kernelOffset)}" - ret.kernel.size = "0x${Integer.toHexString(info.kernelLength)}" + val param = ParamConfig() + ret.kernel.file = param.kernel + ret.kernel.loadOffset = "0x${java.lang.Long.toHexString(info.kernelOffset)}" + ret.kernel.size = "0x${Integer.toHexString(info.kernelLength.toInt())}" ret.kernel.position = "0x${Integer.toHexString(info.kernelPosition)}" ret.ramdisk = CommArgs() - ret.ramdisk!!.loadOffset = "0x${java.lang.Long.toHexString(args.ramdiskOffset)}" - ret.ramdisk!!.size = "0x${Integer.toHexString(info.ramdiskLength)}" + ret.ramdisk!!.loadOffset = "0x${java.lang.Long.toHexString(info.ramdiskOffset)}" + ret.ramdisk!!.size = "0x${Integer.toHexString(info.ramdiskLength.toInt())}" ret.ramdisk!!.position = "0x${Integer.toHexString(info.ramdiskPosition)}" - args.ramdisk?.let { - ret.ramdisk!!.file = args.ramdisk + if (info.ramdiskLength > 0) { + ret.ramdisk!!.file = param.ramdisk } ret.secondBootloader = CommArgs() - ret.secondBootloader!!.loadOffset = "0x${java.lang.Long.toHexString(args.secondOffset)}" - ret.secondBootloader!!.size = "0x${Integer.toHexString(info.secondBootloaderLength)}" + ret.secondBootloader!!.loadOffset = "0x${java.lang.Long.toHexString(info.secondBootloaderOffset)}" + ret.secondBootloader!!.size = "0x${Integer.toHexString(info.secondBootloaderLength.toInt())}" ret.secondBootloader!!.position = "0x${Integer.toHexString(info.secondBootloaderPosition)}" - args.second?.let { - ret.secondBootloader!!.file = args.second + if (info.secondBootloaderLength > 0) { + ret.secondBootloader!!.file = param.second } - if (args.headerVersion > 0) { + if (info.headerVersion > 0) { ret.recoveryDtbo = CommArgs() - args.dtbo?.let { - ret.recoveryDtbo!!.file = args.dtbo + if (info.recoveryDtboLength > 0) { + ret.recoveryDtbo!!.file = param.dtbo } - ret.recoveryDtbo!!.loadOffset = "0x${java.lang.Long.toHexString(args.dtboOffset)}" - ret.recoveryDtbo!!.size = "0x${Integer.toHexString(info.recoveryDtboLength)}" + ret.recoveryDtbo!!.loadOffset = "0x${java.lang.Long.toHexString(info.recoveryDtboOffset)}" + ret.recoveryDtbo!!.size = "0x${Integer.toHexString(info.recoveryDtboLength.toInt())}" ret.recoveryDtbo!!.position = "0x${Integer.toHexString(info.recoveryDtboPosition)}" } - ret.info.output = args.output - ret.info.headerSize = info.headerSize - ret.info.headerVersion = args.headerVersion - ret.info.loadBase = "0x${java.lang.Long.toHexString(args.base)}" - ret.info.board = if (args.board.isBlank()) null else args.board - ret.info.tagsOffset = "0x${java.lang.Long.toHexString(args.tagsOffset)}" - ret.info.cmdline = args.cmdline - ret.info.osVersion = args.osVersion - ret.info.osPatchLevel = args.osPatchLevel - ret.info.hash = Helper.toHexString(info.hash) - ret.info.pageSize = args.pageSize - - ret.info.verify = args.verifyType - ret.signature = info.signature + if (info.headerVersion > 1) { + ret.dtb = CommArgs() + if (info.dtbLength > 0) { + ret.dtb!!.file = param.dtb + } + ret.dtb!!.loadOffset = "0x${java.lang.Long.toHexString(info.dtbOffset)}" + ret.dtb!!.size = "0x${Integer.toHexString(info.dtbLength.toInt())}" + ret.dtb!!.position = "0x${Integer.toHexString(info.dtbPosition)}" + } + //ret.info.output = //unknown + ret.info.headerSize = info.headerSize.toInt() + ret.info.headerVersion = info.headerVersion + ret.info.loadBase = "0x${java.lang.Long.toHexString(0)}" + ret.info.board = if (info.board.isBlank()) null else info.board + ret.info.tagsOffset = "0x${java.lang.Long.toHexString(info.tagsOffset)}" + ret.info.cmdline = info.cmdline + ret.info.osVersion = info.osVersion + ret.info.osPatchLevel = info.osPatchLevel + ret.info.hash = info.hash!! + ret.info.pageSize = info.pageSize + ret.info.verify = info.signatureType!! + ret.info.imageSize = info.imageSize return ret } - fun readBack(): Array { - var ret: Array = arrayOfNulls(3) - val readBack = ObjectMapper().readValue(File(workDir + "bootimg.json"), - UnifiedConfig::class.java).toArgs() - val imgArgs = readBack[0] as ImgArgs - val info = readBack[1] as ImgInfo - if (imgArgs.verifyType == ImgArgs.VerifyType.AVB) { - val sig = ObjectMapper().readValue( - Signer.mapToJson(info.signature as LinkedHashMap<*, *>), ImgInfo.AvbSignature::class.java) - ret[2] = sig - } else { - val sig2 = ObjectMapper().readValue( - Signer.mapToJson(info.signature as LinkedHashMap<*, *>), ImgInfo.VeritySignature::class.java) - ret[2] = sig2 - } - ret[0] = imgArgs - ret[1] = info - return ret + fun readBack2(): BootImgInfo { + val param = ParamConfig() + return ObjectMapper().readValue(File(param.cfg), + UnifiedConfig::class.java).toBootImgInfo() } } } diff --git a/bbootimg/src/main/kotlin/avb/AVBInfo.kt b/bbootimg/src/main/kotlin/avb/AVBInfo.kt index 722674d..9d2290a 100755 --- a/bbootimg/src/main/kotlin/avb/AVBInfo.kt +++ b/bbootimg/src/main/kotlin/avb/AVBInfo.kt @@ -1,53 +1,9 @@ package avb -import avb.desc.* -import cfig.Helper - /* a wonderfaul base64 encoder/decoder: https://cryptii.com/base64-to-hex */ class AVBInfo(var header: Header? = null, var authBlob: AuthBlob? = null, var auxBlob: AuxBlob? = null, - var footer: Footer? = null) { - data class AuthBlob( - var offset: Long = 0L, - var size: Long = 0L, - var hash: String? = null, - var signature: String? = null) - - data class AuxBlob( - var pubkey: PubKeyInfo? = null, - var pubkeyMeta: PubKeyMetadataInfo? = null, - var propertyDescriptor: MutableList = mutableListOf(), - var hashTreeDescriptor: MutableList = mutableListOf(), - var hashDescriptors: MutableList = mutableListOf(), - var kernelCmdlineDescriptor: MutableList = mutableListOf(), - var chainPartitionDescriptor: MutableList = mutableListOf(), - var unknownDescriptors: MutableList = mutableListOf() - ) { - data class PubKeyInfo( - var offset: Long = 0L, - var size: Long = 0L, - var pubkey: ByteArray = byteArrayOf() - ) - - data class PubKeyMetadataInfo( - var offset: Long = 0L, - var size: Long = 0L - ) - - fun encodeDescriptors(): ByteArray { - var descList: MutableList = mutableListOf() - this.hashTreeDescriptor.forEach { descList.add(it) } - this.hashDescriptors.forEach { descList.add(it) } - this.kernelCmdlineDescriptor.forEach { descList.add(it) } - this.chainPartitionDescriptor.forEach { descList.add(it) } - this.unknownDescriptors.forEach { descList.add(it) } - descList.sortBy { it.sequence } - var ret = byteArrayOf() - descList.forEach { ret = Helper.join(ret, it.encode()) } - return ret - } - } -} + var footer: Footer? = null) diff --git a/bbootimg/src/main/kotlin/avb/AuthBlob.kt b/bbootimg/src/main/kotlin/avb/AuthBlob.kt new file mode 100644 index 0000000..2191ce4 --- /dev/null +++ b/bbootimg/src/main/kotlin/avb/AuthBlob.kt @@ -0,0 +1,7 @@ +package avb + +data class AuthBlob( + var offset: Long = 0L, + var size: Long = 0L, + var hash: String? = null, + var signature: String? = null) \ No newline at end of file diff --git a/bbootimg/src/main/kotlin/avb/AuxBlob.kt b/bbootimg/src/main/kotlin/avb/AuxBlob.kt new file mode 100644 index 0000000..1b98b7b --- /dev/null +++ b/bbootimg/src/main/kotlin/avb/AuxBlob.kt @@ -0,0 +1,82 @@ +package avb + +import avb.alg.Algorithm +import avb.desc.* +import cfig.Helper +import cfig.io.Struct +import org.junit.Assert +import org.slf4j.LoggerFactory +import java.nio.file.Files +import java.nio.file.Paths + +data class AuxBlob( + var pubkey: PubKeyInfo? = null, + var pubkeyMeta: PubKeyMetadataInfo? = null, + var propertyDescriptor: MutableList = mutableListOf(), + var hashTreeDescriptor: MutableList = mutableListOf(), + var hashDescriptors: MutableList = mutableListOf(), + var kernelCmdlineDescriptor: MutableList = mutableListOf(), + var chainPartitionDescriptor: MutableList = mutableListOf(), + var unknownDescriptors: MutableList = mutableListOf() +) { + data class PubKeyInfo( + 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() + ) + + fun encodeDescriptors(): ByteArray { + var ret = byteArrayOf() + return mutableListOf().let { descList -> + arrayOf(this.propertyDescriptor, //tag 0 + this.hashTreeDescriptor, //tag 1 + this.hashDescriptors, //tag 2 + this.kernelCmdlineDescriptor, //tag 3 + this.chainPartitionDescriptor, //tag 4 + this.unknownDescriptors //tag X + ).forEach { typedList -> + typedList.forEach { descList.add(it) } + } + descList.sortBy { it.sequence } + descList.forEach { ret = Helper.join(ret, it.encode()) } + ret + } + } + + //encoded_descriptors + encoded_key + pkmd_blob + (padding) + fun encode(): ByteArray { + val encodedDesc = this.encodeDescriptors() + var sumOfSize = encodedDesc.size + this.pubkey?.let { sumOfSize += it.pubkey.size } + this.pubkeyMeta?.let { sumOfSize += it.pkmd.size } + val auxSize = Helper.round_to_multiple(sumOfSize.toLong(), 64) + return Struct("${auxSize}b").pack( + Helper.joinWithNulls(encodedDesc, this.pubkey?.pubkey, this.pubkeyMeta?.pkmd)) + } + + companion object { + fun encodePubKey(alg: Algorithm, key: ByteArray? = null): ByteArray { + var encodedKey = byteArrayOf() + var algKey: ByteArray? = key + if (alg.public_key_num_bytes > 0) { + if (key == null) { + algKey = Files.readAllBytes((Paths.get(alg.defaultKey))) + } + encodedKey = Helper.encodeRSAkey(algKey!!) + log.info("encodePubKey(): size = ${alg.public_key_num_bytes}, algorithm key size: ${encodedKey.size}") + Assert.assertEquals(alg.public_key_num_bytes, encodedKey.size) + } else { + log.info("encodePubKey(): No key to encode for algorithm " + alg.name) + } + return encodedKey + } + + private val log = LoggerFactory.getLogger(AuxBlob::class.java) + } +} diff --git a/bbootimg/src/main/kotlin/avb/Blob.kt b/bbootimg/src/main/kotlin/avb/Blob.kt index 2a7f3b9..ab3fc35 100644 --- a/bbootimg/src/main/kotlin/avb/Blob.kt +++ b/bbootimg/src/main/kotlin/avb/Blob.kt @@ -1,40 +1,21 @@ package avb -import avb.alg.Algorithm import avb.alg.Algorithms import cfig.Helper import cfig.io.Struct -import org.junit.Assert import org.slf4j.LoggerFactory -import java.nio.file.Files -import java.nio.file.Paths import java.security.MessageDigest class Blob { companion object { - fun encodePubKey(alg: Algorithm, key: ByteArray? = null): ByteArray { - var encodedKey = byteArrayOf() - var algKey: ByteArray? = key - if (alg.public_key_num_bytes > 0) { - if (key == null) { - algKey = Files.readAllBytes((Paths.get(alg.defaultKey))) - } - encodedKey = Helper.encodeRSAkey(algKey!!) - log.info("encodePubKey(): size = ${alg.public_key_num_bytes}, algorithm key size: ${encodedKey.size}") - Assert.assertEquals(alg.public_key_num_bytes, encodedKey.size) - } else { - log.info("encodePubKey(): No key to encode for algorithm " + alg.name) - } - return encodedKey - } + private val log = LoggerFactory.getLogger(Blob::class.java) - //TODO: support pkmd_blob //encoded_descriptors + encoded_key + pkmd_blob + (padding) - fun getAuxDataBlob(encodedDesc: ByteArray, encodedKey: ByteArray): ByteArray { + fun getAuxDataBlob(encodedDesc: ByteArray, encodedKey: ByteArray, pkmdBlob: ByteArray): ByteArray { val auxSize = Helper.round_to_multiple( - encodedDesc.size + encodedKey.size /* encoded key */ + 0L /* pkmd_blob */, + (encodedDesc.size + encodedKey.size + pkmdBlob.size).toLong(), 64) - return Struct("${auxSize}b").pack(Helper.join(encodedDesc, encodedKey)) + return Struct("${auxSize}b").pack(Helper.join(encodedDesc, encodedKey, pkmdBlob)) } fun getAuthBlob(header_data_blob: ByteArray, @@ -42,7 +23,7 @@ class Blob { algorithm_name: String): ByteArray { val alg = Algorithms.get(algorithm_name)!! val authBlockSize = Helper.round_to_multiple((alg.hash_num_bytes + alg.signature_num_bytes).toLong(), 64) - if (authBlockSize == 0L) { + if (0L == authBlockSize) { log.info("No auth blob") return byteArrayOf() } @@ -61,7 +42,5 @@ class Blob { val authData = Helper.join(binaryHash, binarySignature) return Helper.join(authData, Struct("${authBlockSize - authData.size}x").pack(0)) } - - private val log = LoggerFactory.getLogger(Blob::class.java) } -} \ No newline at end of file +} diff --git a/bbootimg/src/main/kotlin/avb/ByteArraySerializer.kt b/bbootimg/src/main/kotlin/avb/ByteArraySerializer.kt deleted file mode 100755 index 0c76a24..0000000 --- a/bbootimg/src/main/kotlin/avb/ByteArraySerializer.kt +++ /dev/null @@ -1,16 +0,0 @@ -package avb - -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.databind.JsonSerializer -import com.fasterxml.jackson.databind.SerializerProvider -import org.apache.commons.codec.binary.Hex - -class ByteArraySerializer: JsonSerializer() { - override fun serialize(value: ByteArray?, gen: JsonGenerator?, serializers: SerializerProvider?) { - if (value != null) { - gen!!.writeString(Hex.encodeHexString(value!!)) - } else { - gen!!.writeString("") - } - } -} \ No newline at end of file diff --git a/bbootimg/src/main/kotlin/avb/Footer.kt b/bbootimg/src/main/kotlin/avb/Footer.kt index 15a8b6e..c918bb7 100644 --- a/bbootimg/src/main/kotlin/avb/Footer.kt +++ b/bbootimg/src/main/kotlin/avb/Footer.kt @@ -3,6 +3,23 @@ package avb import cfig.io.Struct import org.junit.Assert import java.io.InputStream +/* +https://github.com/cfig/Android_boot_image_editor/blob/master/doc/layout.md#32-avb-footer-vboot-20 + ++---------------------------------------+-------------------------+ --> partition_size - block_size +| Padding | block_size - 64 | ++---------------------------------------+-------------------------+ --> partition_size - 64 +| AVB Footer | total 64 | +| | | +| - Footer Magic "AVBf" | 4 | +| - Footer Major Version | 4 | +| - Footer Minor Version | 4 | +| - Original image size | 8 | +| - VBMeta offset | 8 | +| - VBMeta size | 8 | +| - Padding | 28 | ++---------------------------------------+-------------------------+ --> partition_size + */ data class Footer constructor( var versionMajor: Long = FOOTER_VERSION_MAJOR, @@ -14,7 +31,7 @@ data class Footer constructor( companion object { const val MAGIC = "AVBf" const val SIZE = 64 - const val RESERVED = 28 + private const val RESERVED = 28 const val FOOTER_VERSION_MAJOR = 1L const val FOOTER_VERSION_MINOR = 0L private const val FORMAT_STRING = "!4s2L3Q${RESERVED}x" diff --git a/bbootimg/src/main/kotlin/avb/Header.kt b/bbootimg/src/main/kotlin/avb/Header.kt index bdeee84..6562390 100644 --- a/bbootimg/src/main/kotlin/avb/Header.kt +++ b/bbootimg/src/main/kotlin/avb/Header.kt @@ -6,6 +6,7 @@ import cfig.io.Struct import org.junit.Assert import java.io.InputStream +//avbtool::AvbVBMetaHeader data class Header( var required_libavb_version_major: Int = Avb.AVB_VERSION_MAJOR, var required_libavb_version_minor: Int = 0, @@ -25,10 +26,6 @@ data class Header( var rollback_index: Long = 0L, var flags: Long = 0, var release_string: String = "avbtool ${Avb.AVB_VERSION_MAJOR}.${Avb.AVB_VERSION_MINOR}.${Avb.AVB_VERSION_SUB}") { - fun bump_required_libavb_version_minor(minor: Int) { - this.required_libavb_version_minor = maxOf(required_libavb_version_minor, minor) - } - @Throws(IllegalArgumentException::class) constructor(iS: InputStream) : this() { val info = Struct(FORMAT_STRING).unpack(iS) @@ -53,7 +50,7 @@ data class Header( this.descriptors_size = info[15] as Long this.rollback_index = info[16] as Long this.flags = info[17] as Long - //padding + //padding: info[18] this.release_string = Helper.toCString(info[19] as ByteArray) } @@ -70,17 +67,21 @@ data class Header( this.descriptors_offset, this.descriptors_size, this.rollback_index, this.flags, - null, - this.release_string.toByteArray(), - null, - null) + null, //${REVERSED0}x + this.release_string.toByteArray(), //47s + null, //x + null) //${REVERSED}x + } + + fun bump_required_libavb_version_minor(minor: Int) { + this.required_libavb_version_minor = maxOf(required_libavb_version_minor, minor) } companion object { const val magic: String = "AVB0" const val SIZE = 256 - const val REVERSED0 = 4 - const val REVERSED = 80 + private const val REVERSED0 = 4 + private const val REVERSED = 80 const val FORMAT_STRING = ("!4s2L2QL11QL${REVERSED0}x47sx" + "${REVERSED}x") init { diff --git a/bbootimg/src/main/kotlin/avb/VBMeta.kt b/bbootimg/src/main/kotlin/avb/VBMeta.kt new file mode 100644 index 0000000..5d05175 --- /dev/null +++ b/bbootimg/src/main/kotlin/avb/VBMeta.kt @@ -0,0 +1,6 @@ +package avb + +class VBMeta(var header: Header? = null, + var authBlob: AuthBlob? = null, + var auxBlob: AuxBlob? = null) { +} \ No newline at end of file diff --git a/bbootimg/src/main/kotlin/avb/desc/PropertyDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/PropertyDescriptor.kt index cefe77b..189b275 100755 --- a/bbootimg/src/main/kotlin/avb/desc/PropertyDescriptor.kt +++ b/bbootimg/src/main/kotlin/avb/desc/PropertyDescriptor.kt @@ -5,8 +5,8 @@ import cfig.io.Struct import java.io.InputStream class PropertyDescriptor( - var key: String = "", - var value: String = "") : Descriptor(TAG, 0, 0) { + private var key: String = "", + private var value: String = "") : Descriptor(TAG, 0, 0) { override fun encode(): ByteArray { if (SIZE != Struct(FORMAT_STRING).calcSize()) { throw RuntimeException() diff --git a/bbootimg/src/main/kotlin/bootimg/BootImgHeader.kt b/bbootimg/src/main/kotlin/bootimg/BootImgHeader.kt new file mode 100644 index 0000000..00c02a2 --- /dev/null +++ b/bbootimg/src/main/kotlin/bootimg/BootImgHeader.kt @@ -0,0 +1,295 @@ +package cfig.bootimg + +import cfig.Helper +import cfig.ParamConfig +import cfig.io.Struct +import com.fasterxml.jackson.databind.ObjectMapper +import org.junit.Assert +import org.slf4j.LoggerFactory +import java.io.File +import java.io.FileInputStream +import java.io.InputStream +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.security.MessageDigest +import java.util.regex.Pattern + +open class BootImgHeader( + var kernelLength: Long = 0, + var kernelOffset: Long = 0, + + var ramdiskLength: Long = 0, + var ramdiskOffset: Long = 0, + + var secondBootloaderLength: Long = 0, + var secondBootloaderOffset: Long = 0, + + var recoveryDtboLength: Long = 0, + var recoveryDtboOffset: Long = 0, + + var dtbLength: Long = 0, + var dtbOffset: Long = 0, + + var tagsOffset: Long = 0, + + var pageSize: Int = 0, + + var headerSize: Long = 0, + var headerVersion: Int = 0, + + var board: String = "", + + var cmdline: String = "", + + var hash: ByteArray? = null, + + var osVersion: String? = null, + var osPatchLevel: String? = null) { + @Throws(IllegalArgumentException::class) + constructor(iS: InputStream?) : this() { + if (iS == null) { + return + } + log.warn("BootImgHeader constructor") + val info = Struct(FORMAT_STRING).unpack(iS) + Assert.assertEquals(20, info.size) + if (!(info[0] as ByteArray).contentEquals(magic.toByteArray())) { + throw IllegalArgumentException("stream doesn't look like Android Boot Image Header") + } + this.kernelLength = info[1] as Long + this.kernelOffset = info[2] as Long + this.ramdiskLength = info[3] as Long + this.ramdiskOffset = info[4] as Long + this.secondBootloaderLength = info[5] as Long + this.secondBootloaderOffset = info[6] as Long + this.tagsOffset = info[7] as Long + this.pageSize = (info[8] as Long).toInt() + this.headerVersion = (info[9] as Long).toInt() + val osNPatch = (info[10] as Long).toInt() + if (0 != osNPatch) { //treated as 'reserved' in this boot image + this.osVersion = parseOsVersion(osNPatch shr 11) + this.osPatchLevel = parseOsPatchLevel(osNPatch and 0x7ff) + } + this.board = Helper.toCString(info[11] as ByteArray).trim() + this.cmdline = Helper.toCString(info[12] as ByteArray) + Helper.toCString(info[14] as ByteArray) + this.hash = info[13] as ByteArray + + if (this.headerVersion > 0) { + this.recoveryDtboLength = info[15] as Long + this.recoveryDtboOffset = info[16] as Long + } + + this.headerSize = info[17] as Long + assert(this.headerSize.toInt() in intArrayOf(BOOT_IMAGE_HEADER_V2_SIZE, BOOT_IMAGE_HEADER_V1_SIZE)) + + if (this.headerVersion > 1) { + this.dtbLength = info[18] as Long + this.dtbOffset = info[19] as Long + } + } + + private fun parseOsVersion(x: Int): String { + val a = x shr 14 + val b = x - (a shl 14) shr 7 + val c = x and 0x7f + return String.format("%d.%d.%d", a, b, c) + } + + private fun parseOsPatchLevel(x: Int): String { + var y = x shr 4 + val m = x and 0xf + y += 2000 + return String.format("%d-%02d-%02d", y, m, 0) + } + + @Throws(IllegalArgumentException::class) + private fun packOsVersion(x: String?): Int { + if (x.isNullOrBlank()) return 0 + val pattern = Pattern.compile("^(\\d{1,3})(?:\\.(\\d{1,3})(?:\\.(\\d{1,3}))?)?") + val m = pattern.matcher(x) + if (m.find()) { + val a = Integer.decode(m.group(1)) + var b = 0 + var c = 0 + if (m.groupCount() >= 2) { + b = Integer.decode(m.group(2)) + } + if (m.groupCount() == 3) { + c = Integer.decode(m.group(3)) + } + Assert.assertTrue(a < 128) + Assert.assertTrue(b < 128) + Assert.assertTrue(c < 128) + return (a shl 14) or (b shl 7) or c + } else { + throw IllegalArgumentException("invalid os_version") + } + } + + private fun packOsPatchLevel(x: String?): Int { + if (x.isNullOrBlank()) return 0 + val ret: Int + val pattern = Pattern.compile("^(\\d{4})-(\\d{2})-(\\d{2})") + val matcher = pattern.matcher(x) + if (matcher.find()) { + val y = Integer.parseInt(matcher.group(1), 10) - 2000 + val m = Integer.parseInt(matcher.group(2), 10) + // 7 bits allocated for the year, 4 bits for the month + Assert.assertTrue(y in 0..127) + Assert.assertTrue(m in 1..12) + ret = (y shl 4) or m + } else { + throw IllegalArgumentException("invalid os_patch_level") + } + + return ret + } + + @Throws(CloneNotSupportedException::class) + private fun hashFileAndSize(vararg inFiles: String?): ByteArray { + val md = MessageDigest.getInstance("SHA1") + for (item in inFiles) { + if (null == item) { + md.update(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN) + .putInt(0) + .array()) + log.debug("update null $item: " + Helper.toHexString((md.clone() as MessageDigest).digest())) + } else { + val currentFile = File(item) + FileInputStream(currentFile).use { iS -> + var byteRead: Int + var dataRead = ByteArray(1024) + while (true) { + byteRead = iS.read(dataRead) + if (-1 == byteRead) { + break + } + md.update(dataRead, 0, byteRead) + } + log.debug("update file $item: " + Helper.toHexString((md.clone() as MessageDigest).digest())) + md.update(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN) + .putInt(currentFile.length().toInt()) + .array()) + log.debug("update SIZE $item: " + Helper.toHexString((md.clone() as MessageDigest).digest())) + } + } + } + + return md.digest() + } + + private fun refresh() { + val param = ParamConfig() + //refresh kernel size + if (0L == this.kernelLength) { + throw java.lang.IllegalArgumentException("kernel size can not be 0") + } else { + this.kernelLength = File(param.kernel).length() + } + //refresh ramdisk size + if (0L == this.ramdiskLength) { + param.ramdisk = null + } else { + this.ramdiskLength = File(param.ramdisk).length() + } + //refresh second bootloader size + if (0L == this.secondBootloaderLength) { + param.second = null + } else { + this.secondBootloaderLength = File(param.second).length() + } + //refresh recovery dtbo size + if (0L == this.recoveryDtboLength) { + param.dtbo = null + } else { + this.recoveryDtboLength = File(param.dtbo).length() + } + //refresh recovery dtbo size + if (0L == this.dtbLength) { + param.dtb = null + } else { + this.dtbLength = File(param.dtb).length() + } + + //refresh image hash + val imageId = when (this.headerVersion) { + 0 -> { + hashFileAndSize(param.kernel, param.ramdisk, param.second) + } + 1 -> { + hashFileAndSize(param.kernel, param.ramdisk, param.second, param.dtbo) + } + 2 -> { + hashFileAndSize(param.kernel, param.ramdisk, param.second, param.dtbo, param.dtb) + } + else -> { + throw java.lang.IllegalArgumentException("headerVersion ${this.headerVersion} illegal") + } + } + this.hash = imageId + } + + fun encode(): ByteArray { + this.refresh() + val ret = Struct(FORMAT_STRING).pack( + "ANDROID!".toByteArray(), + //10I + this.kernelLength, + this.kernelOffset, + this.ramdiskLength, + this.ramdiskOffset, + this.secondBootloaderLength, + this.secondBootloaderOffset, + this.tagsOffset, + this.pageSize, + this.headerVersion, + (packOsVersion(this.osVersion) shl 11) or packOsPatchLevel(this.osPatchLevel), + //16s + this.board.toByteArray(), + //512s + this.cmdline.substring(0, minOf(512, this.cmdline.length)).toByteArray(), + //32s + this.hash!!, + //1024s + if (this.cmdline.length > 512) this.cmdline.substring(512).toByteArray() else byteArrayOf(0), + //I + this.recoveryDtboLength, + //Q + if (this.headerVersion > 0) this.recoveryDtboOffset else 0, + //I + when (this.headerVersion) { + 0 -> 0 + 1 -> BOOT_IMAGE_HEADER_V1_SIZE + 2 -> BOOT_IMAGE_HEADER_V2_SIZE + else -> java.lang.IllegalArgumentException("headerVersion ${this.headerVersion} illegal") + }, + //I + this.dtbLength, + //Q + if (this.headerVersion > 1) this.dtbOffset else 0 + ) + return ret + } + + companion object { + internal val log = LoggerFactory.getLogger(BootImgInfo::class.java) + const val magic = "ANDROID!" + const val FORMAT_STRING = "8s" + //"ANDROID!" + "10I" + + "16s" + //board name + "512s" + //cmdline part 1 + "32s" + //hash digest + "1024s" + //cmdline part 2 + "I" + //dtbo length [v1] + "Q" + //dtbo offset [v1] + "I" + //header size [v1] + "I" + //dtb length [v2] + "Q" //dtb offset [v2] + const val BOOT_IMAGE_HEADER_V2_SIZE = 1660 + const val BOOT_IMAGE_HEADER_V1_SIZE = 1648 + + init { + Assert.assertEquals(BOOT_IMAGE_HEADER_V2_SIZE, Struct(FORMAT_STRING).calcSize()) + } + } +} diff --git a/bbootimg/src/main/kotlin/bootimg/BootImgInfo.kt b/bbootimg/src/main/kotlin/bootimg/BootImgInfo.kt new file mode 100644 index 0000000..a63c2ec --- /dev/null +++ b/bbootimg/src/main/kotlin/bootimg/BootImgInfo.kt @@ -0,0 +1,120 @@ +package cfig.bootimg + +import cfig.ParamConfig +import org.apache.commons.exec.CommandLine +import java.io.InputStream + +class BootImgInfo(iS: InputStream?) : BootImgHeader(iS) { + constructor() : this(null) + + val kernelPosition: Int + get() { + return getHeaderSize(this.pageSize) + } + + val ramdiskPosition: Int + get() { + return (kernelPosition + this.kernelLength + + getPaddingSize(this.kernelLength.toInt(), this.pageSize)).toInt() + } + + val secondBootloaderPosition: Int + get() { + return (ramdiskPosition + ramdiskLength + + getPaddingSize(ramdiskLength.toInt(), pageSize)).toInt() + } + + val recoveryDtboPosition: Int + get() { + return (secondBootloaderPosition + secondBootloaderLength + + getPaddingSize(secondBootloaderLength.toInt(), pageSize)).toInt() + } + + val dtbPosition: Int + get() { + return (recoveryDtboPosition + dtbLength + + getPaddingSize(dtbLength.toInt(), pageSize)).toInt() + } + + var signatureType: BootImgInfo.VerifyType? = null + + var imageSize: Long = 0 + + private fun getHeaderSize(pageSize: Int): Int { + val pad = (pageSize - (1648 and (pageSize - 1))) and (pageSize - 1) + return pad + 1648 + } + + private fun getPaddingSize(position: Int, pageSize: Int): Int { + return (pageSize - (position and pageSize - 1)) and (pageSize - 1) + } + + fun toCommandLine(): CommandLine { + val param = ParamConfig() + val ret = CommandLine(param.mkbootimg) + ret.addArgument(" --header_version ") + ret.addArgument(headerVersion.toString()) + ret.addArgument(" --base ") + ret.addArgument("0x" + java.lang.Long.toHexString(0)) + ret.addArgument(" --kernel ") + ret.addArgument(param.kernel) + ret.addArgument(" --kernel_offset ") + ret.addArgument("0x" + java.lang.Long.toHexString(kernelOffset)) + if (this.ramdiskLength > 0) { + ret.addArgument(" --ramdisk ") + ret.addArgument(param.ramdisk) + } + ret.addArgument(" --ramdisk_offset ") + ret.addArgument("0x" + java.lang.Long.toHexString(ramdiskOffset)) + if (this.secondBootloaderLength > 0) { + ret.addArgument(" --second ") + ret.addArgument(param.second) + } + ret.addArgument(" --second_offset ") + ret.addArgument("0x" + java.lang.Long.toHexString(this.secondBootloaderOffset)) + if (!board.isBlank()) { + ret.addArgument(" --board ") + ret.addArgument(board) + } + if (headerVersion > 0) { + if (this.recoveryDtboLength > 0) { + ret.addArgument(" --recovery_dtbo ") + ret.addArgument(param.dtbo) + } + } + if (headerVersion > 1) { + if (this.dtbLength > 0) { + ret.addArgument("--dtb ") + ret.addArgument(param.dtb) + } + ret.addArgument("--dtb_offset ") + ret.addArgument("0x" + java.lang.Long.toHexString(this.dtbOffset)) + } + ret.addArgument(" --pagesize ") + ret.addArgument(Integer.toString(pageSize)) + ret.addArgument(" --cmdline ") + ret.addArgument(cmdline, false) + if (!osVersion.isNullOrBlank()) { + ret.addArgument(" --os_version ") + ret.addArgument(osVersion) + } + if (!osPatchLevel.isNullOrBlank()) { + ret.addArgument(" --os_patch_level ") + ret.addArgument(osPatchLevel) + } + ret.addArgument(" --tags_offset ") + ret.addArgument("0x" + java.lang.Long.toHexString(tagsOffset)) + ret.addArgument(" --id ") + ret.addArgument(" --output ") + //ret.addArgument("boot.img" + ".google") + + log.info("To Commandline: " + ret.toString()) + + return ret + } + + enum class VerifyType { + VERIFY, + AVB + } +} diff --git a/bbootimg/src/test/kotlin/avb/BlobTest.kt b/bbootimg/src/test/kotlin/avb/BlobTest.kt index fe05072..32827b5 100644 --- a/bbootimg/src/test/kotlin/avb/BlobTest.kt +++ b/bbootimg/src/test/kotlin/avb/BlobTest.kt @@ -9,7 +9,7 @@ class BlobTest { @Test fun testEncodedKey2048() { val keyStr = "2d2d2d2d2d424547494e205253412050524956415445204b45592d2d2d2d2d0a4d4949456f77494241414b4341514541786c56523354496b6f75414f7648373976614a54674668706676564b514965566b46525a5056584b2f7a5930477672680a344a4171476a4a6f572f50667251763573644433367174484833612b4735684c5a364e692b742f6d74666a7563785a66754c4743336b6d4a3154335871454b5a0a67585849324952377656536f496d5245764451474544794a7774487a4c414e6c6b624767306367685668575a5343416e644f3842656e616c43327639342f72740a44666b50656b48366467553353663430543073425365535939346d4f7a5461714f52327066563172576c4c5264576d6f33337a654842763532526c627430644d0a755841757265585769487a746b6d35474342433164674d2b4361784e74697a4e45674339314b63443078755243434d325778482b72316c70737a79494a4463740a596272466d5645596c2f6b6a517061666879374e736b316671535479526472695a53596d5451494441514142416f49424151432b6b4a6761437558387759416e0a5358575130666d645a6c586e4d4e5270634630613070443053417a47623152645942584d615869717479686977633533505078734344644e65636a6179494d640a6a4a56585054774c685472754f674d532f6270336763675777563334554856344c4a58474f4741452b6a625330686244424d6975644f596d6a36526d567368700a7a3947317a5a4353514e4d584861577345596b58353958707a7a6f423338346e52756c3251674574777a554e5239586c707a67744a424c6b335341436b76734e0a6d512f445738495748584c6738764c6e314c7a564a32653342313648344d6f45325443487871664d67723033494452524a6f676b656e517551734668657659540a6f2f6d4a79485357617656677a4d48473949356d2b65657046345779686a31593457794b41754d492b39644841582f68374c74385846435143683544626b56470a7a47723334735742416f4742414f73376e37595a714e616167756f7666496452527378785a7231794a41794473723677337947496d445a596a753463345759390a3565734f326b50334641347030633746685146356f4f623172427548455070333663704c346147654b38376361715466713633575a41756a6f545a7072394c700a4252626b4c37772f7847376a70512f636c70413873487a484751732f6e656c786f4f744337453131384669526776442f6a64686c4d794c39416f4742414e66580a76796f4e3170706c665432785238514f6a535a2b513335532f2b5341744d75426e4878336c307148326262426a63764d314d4e44576a6e5244796159686952750a692b4b4137747166696230392b58704233673544364f76376c732f4c647830532f56636d565774696132484b387938694c47746f6b6f425a4b513541614658320a695155382b744334683639476e4a59514b714e776743557a68382b674858355934366f4469546d52416f474159704f78386c582b637a42382f4461364d4e72570a6d495a4e543861745a4c4573447332414e455652784453496354435a4a4964372b6d31572b6e526f6179634c54574e6f775a312b3245724c765231302b4147590a62375973373957673969645961593979476e396c6e5a734d7a4169754c6579497658635371676a76414b6c565772684f51464f756768764e5776466c383559790a6f5753434d6c5069544c747437434373434b73674b7545436759426764497036475a7349666b67636c4b653068716776526f65553454523367636a4a6c4d39410a6c42546f2b704b686142656374706c783952785238416e73506f626271776361486e496641754b447a6a6b356d45764b5a6a436c6e46584634484148627941460a6e527a5a457939586b57466863383054357252705a4f3743377164786d753261694b69784d3356334c332f3055353871554c4544627562484d773962456841540a5075644938514b4267484545694d6d2f687239543431686251692f4c59616e576e6c46773175652b6f734b75463862585175786e6e484e7546542f632b392f410a76576867714736624f4548752b702f495072596d3474424d596c77737968346e5843794767444a4c624c49667a4b774b4157437448394c776e794456684f6f770a474839736864522b7357334577393778656630324b414834566c4e414e456d42563473514e7157577673597263466d32724f644c0a2d2d2d2d2d454e44205253412050524956415445204b45592d2d2d2d2d0a" - val encodedKey = Blob.encodePubKey(Algorithms.get("SHA512_RSA2048")!!, Hex.decodeHex(keyStr)) + val encodedKey = AuxBlob.encodePubKey(Algorithms.get("SHA512_RSA2048")!!, Hex.decodeHex(keyStr)) val expectedKeyEnc = "00000800c9d87d7bc65551dd3224a2e00ebc7efdbda2538058697ef54a4087959054593d55caff36341afae1e0902a1a32685bf3dfad0bf9b1d0f7eaab471f76be1b984b67a362fadfe6b5f8ee73165fb8b182de4989d53dd7a842998175c8d8847bbd54a8226444bc3406103c89c2d1f32c036591b1a0d1c82156159948202774ef017a76a50b6bfde3faed0df90f7a41fa76053749fe344f4b0149e498f7898ecd36aa391da97d5d6b5a52d17569a8df7cde1c1bf9d9195bb7474cb9702eade5d6887ced926e460810b576033e09ac4db62ccd1200bdd4a703d31b910823365b11feaf5969b33c8824372d61bac599511897f92342969f872ecdb24d5fa924f245dae26526264d1efa3b53abc5d379532f66b82194669a936f3526438c8f96b9ffaccdf4006ebeb673548450854653d5dd43feb26a784079569f86f3d3813b3d409035329a517ff8c34bc7d6a1ca30fb1bfd270ab8644134c117dea1769aebcf0c50d913f50d0b2c9924cbb5b4f8c60ad026b15bfd4d44669db076aa799dc05c3b9436c18ffec9d25a6aa046e1a28b2f516151a3369183b4fbcda9403446988a1a91dfd92c3abf573a464620f2f0bc315e29fe5890325c6697999a8e1523eba947d363c618bb8ed2209f78af71b32e0889a1f17db130a0e61bbd6ec8f633e1dab0bd1641fe076f6e978b6a33d2d780036a4de70582281fef6aa9757ee14ee2955b4fe6dc03b981" assertEquals(expectedKeyEnc, Hex.encodeHexString(encodedKey)) } @@ -17,7 +17,7 @@ class BlobTest { @Test fun testEncodeKey4096() { val keyStr = "2d2d2d2d2d424547494e205253412050524956415445204b45592d2d2d2d2d0a4d49494a4b51494241414b43416745413241537634394f456248344e695433436a4e4d5356656c697966455058737757637174456643786c53705331466973410a757762764577645454506c6b7553683647345359694e686e704350357030766353672f334f686975564b67562f724374724458614f36306e764b2f6f307938330a4e4e5a524b3278614a396557427139727549444b2b6a43307359577a546171717778593047726a6e782f7235435865726c355072524b3750494c7a77674248620a4977784863626c74316e74675234635756704f337769716173457742444444596b3466773757364c766a42623971617633594238525636506b5a4e65525036340a676766756563712f4d584e69574f504e784c7a434552326853722f2b4a333268396a576a587372635679382b384d6c64686d72347232616e37633234376146660a757075464774554a7270524f4f382f4c584d6c356750664d706b716f61746a544d52483539674a6a4b686f743052706d47785a42766233335463424b3553644a0a583339593479637435636c6d446c4934466a6a3746757454502b623936614a654a566e596555582f4130776d6f6742616a734a526f525835652f5263675a73590a527a58594c515870725138316442576a6a6f764d4a39703858655436424e4d4643376f36736b6c464c3066484455452f6c34424e5038473175334266707a65760a534349535253373144346553346f51422b5249504642556b7a6f6d5a37726e45463342774665712b786d7766597250304c5261482b315965526175754d7552650a6b6531545a6c36393761336d456a6b4e67386e6f6132777470653745576d61756a4a66584457784a782f58456b6a474c4365347a32716b33746b6b592b4135670a5263677a6b6538675678432b654332444a74624b59666b76344c38464d464a61456877417031334d664337466c59756a4f2f42444c6c3764414e7343417745410a41514b43416741576f4c38502f57736b746a755377623573592f764b74677a634848314172393432477379737554585044793638364c7046335238542f6a4e790a6e376b3255424169613878536f5743523642625275486556356f412b504c47654f704537516153666f6e422b79632b63793078334f7233737366714573752f710a746f47487037352f38445853365745304b303478393475317264433962397350727247426c57434c477a714d306b62754a667948586464336e32536f6641554f0a62355152536778442b327448557045726f4871486e574a436166344a30516567583435796b746c664f594e4b2f50484c44515856386c792f656a6333324d34590a5476376855744f4f4a54757138564367394f575a6d325a6f3151754d3958454a54504370356c332b6f35767a4f3679686b32676f7444764433324364412b336b0a744c4a525035344d31536e2b4958623167474b4e39724b4174474a62656e5749506c4e4f626851676b627747383951642b3572664d587369507631486c31744b0a2b7471776a4438322f48332f456c61614d6e77484370656f47537039354f626c416f426a7a6a4d50324b7362764b53644c384f2f7266316333754f77392b44460a6374683053413879335a7a493131674a746232514d475572436e79356e34735047476263337833384e644c6877626b504b5a7936304f69543467326b4e7064590a644969746d414d4c326f747474694634414a4d36417261506b3859567a6b504c546b736f4c33617a50427961356c496f444932483351765474537670586b58500a794b6368734453575962647166706c71432f5830446a70322f5a64386a704e3549362b3161536d70546d6277782f4a546c6c59314e383946525a4c4964786f680a326b38314c5069586845367552626a696f4a556c626e45574970593279324e32436c6d78706a68302f496358643158496d514b4341514541375a61692b796a6a0a387869743234614f395466336d5a4258426a5361446f646a43324b533179436341495870365337614830775a6970795a70516a7973337a614251794d525946470a625171496656416136696e5779446f6f6662414a484d7535425663484642505a765353355968446a6338585a3564715343787a497a396f70497141626d2b62340a6145562f3341334a6b6935447938792f356a323147414b3459346d71514f597a6e653762444769334879753034314d474d3471664963496b53354e31654857340a73445a4a68362b4b357475784e355458336e445a53706d396c754e48386d4c47674b415a313562314c715841744d3579636f4259394876303832737550506f6d0a4f2b72307962645258366e445348382b313179324b6950326b645649554843476b776c716772757835595a796a435a50774f764550687a536f4f532b764269460a555658413869646e784e4c6b31514b4341514541364d496968445358782b33353066577168512f3351633667412f74324331354a774a392b754657412b676a640a632f686e3548636d6e6d424a4e345230346e4c472f61553953517572383761346d6e432f4d70394a4941526a486c5a2f574e54345530734a79504556526735550a5a3956616a417563577769304a794a59434f31454d4d7936384a7038716c5472694b2f4c376e624438364a4a354153786a6f6a694e2f3070734b2f506b3630460a52722b73684b5069336a52513142446a447441784f666f346374662f6e4662554d34625930464e50514d50375765736f534b55304e42435252366430643274710a59666c4d6a495148782b4e373450356a4564534348545647516d2b646a3437705574336c4c504c576330625831472f47656b775850344e5573522f37304873690a6277786b4e6e4b325453477a6b743272634f6e757450313235724a753657705637534e727139726d37774b43415141664d524f636e625776694b48716e4450510a6864522f324b39554a54764568496e41534f5332555a5770692b733172657a394275536a69674f7834776261415a3474343450573743337579743834644866550a486b495162334935626738454e4d724a704b394e4e3333796b77757a6b44774d537746635a2b4763693937685375627a6f4d6c2f496b6569694e314d61704c340a47684c556773442b33554d564c2b593953796d4b383633374967796f434764694e44362f535873613853774c4a6f3356546a717834654b70583763766c53424c0a52725278633530546d775573416873643443446c39596e5341544c6a56764a4265596c664d32746246506159776c31615238762b50576b666e4b3065666d36300a66486b69333348456e47746542504b7547713476775659706e3662594777517a2b66363333352f4132444d665a484653706a56555248506352634862434d6c610a30635578416f4942415143323565594e6b4f3437386d6f2b62426245584a6c6b6f714c6d766a417947724e466f343846396c705648365930764e75576b584a4e0a5055674c556841753652596f746a47454e71473137727a387a742f505059394f6b325033734f783874303079316d496e2f686c445a58733535464d30664f4d750a505a616973634150733748447a76794f6d4461682b667a692b5a443848324d33445332572b5945306961654a6132765a4a5332743032573042475869444933330a495a44714d794c59767777506a4f6e53684a7964457a58494434784c6c30744e6a7a4c786f3347534e41376a59716c6d627456384358496337724d534c3657560a6b7449444b4b4a636e6d706e3354634b6558364d456a615349543832704e4f5333665933506d58754c2b434d7a6677382b75373745656371373866486154694c0a50354a474d393346366d7a693139455930746d496e55424d435774514c63454e416f494241514367304b614f6b6238543336717a5072746762666f75304532440a756664704c3175676d443465644f464b51423566444651684c6e534556534a71334b5567346b57735861705164734264366b4c6478532b4b364d51724c427a720a34746630633755434631417a576b3677584d45785a386d526232526b475a595142324464796846423354506d6e71394357384a43712b366b78672f776b5534730a764d344a587a676371566f53663432514a6c2b4239776165576867304254577830316c616c34647338384876454b6d4530696b354777694462723745764444770a453655625a745163496f535449495a4467597156466652324441686f3377584a52734f58683433336c454a3858376343447a726e674662516e6c4b7270774d4c0a58676d30534955632b4e6635706f4d4d3372664c464b3737742f6f6234772b355077524b636f536e69794178724864366277796b59413856757964760a2d2d2d2d2d454e44205253412050524956415445204b45592d2d2d2d2d0a" - val encodedKey = Blob.encodePubKey(Algorithms.get("SHA256_RSA4096")!!, Hex.decodeHex(keyStr)) + val encodedKey = AuxBlob.encodePubKey(Algorithms.get("SHA256_RSA4096")!!, Hex.decodeHex(keyStr)) val expectedKeyEnc = "0000100055d904add804afe3d3846c7e0d893dc28cd31255e962c9f10f5ecc1672ab447c2c654a94b5162b00bb06ef1307534cf964b9287a1b849888d867a423f9a74bdc4a0ff73a18ae54a815feb0adac35da3bad27bcafe8d32f3734d6512b6c5a27d79606af6bb880cafa30b4b185b34daaaac316341ab8e7c7faf90977ab9793eb44aecf20bcf08011db230c4771b96dd67b604787165693b7c22a9ab04c010c30d89387f0ed6e8bbe305bf6a6afdd807c455e8f91935e44feb88207ee79cabf31736258e3cdc4bcc2111da14abffe277da1f635a35ecadc572f3ef0c95d866af8af66a7edcdb8eda15fba9b851ad509ae944e3bcfcb5cc97980f7cca64aa86ad8d33111f9f602632a1a2dd11a661b1641bdbdf74dc04ae527495f7f58e3272de5c9660e52381638fb16eb533fe6fde9a25e2559d87945ff034c26a2005a8ec251a115f97bf45c819b184735d82d05e9ad0f357415a38e8bcc27da7c5de4fa04d3050bba3ab249452f47c70d413f97804d3fc1b5bb705fa737af482212452ef50f8792e28401f9120f141524ce8999eeb9c417707015eabec66c1f62b3f42d1687fb561e45abae32e45e91ed53665ebdedade612390d83c9e86b6c2da5eec45a66ae8c97d70d6c49c7f5c492318b09ee33daa937b64918f80e6045c83391ef205710be782d8326d6ca61f92fe0bf0530525a121c00a75dcc7c2ec5958ba33bf0432e5edd00db0db33799a9cd9cb743f7354421c28271ab8daab44111ec1e8dfc1482924e836a0a6b355e5de95ccc8cde39d14a5b5f63a964e00acb0bb85a7cc30be6befe8b0f7d348e026674016cca76ac7c67082f3f1aa62c60b3ffda8db8120c007fcc50a15c64a1e25f3265c99cbed60a13873c2a45470cca4282fa8965e789b48ff71ee623a5d059377992d7ce3dfde3a10bcf6c85a065f35cc64a635f6e3a3a2a8b6ab62fbbf8b24b62bc1a912566e369ca60490bf68abe3e7653c27aa8041775f1f303621b85b2b0ef8015b6d44edf71acdb2a04d4b421ba655657e8fa84a27d130eafd79a582aa381848d09a06ac1bbd9f586acbd756109e68c3d77b2ed3020e4001d97e8bfc7001b21b116e741672eec38bce51bb4062331711c49cd764a76368da3898b4a7af487c8150f3739f66d8019ef5ca866ce1b167921dfd73130c421dd345bd21a2b3e5df7eaca058eb7cb492ea0e3f4a74819109c04a7f42874c86f63202b462426191dd12c316d5a29a206a6b241cc0a27960996ac476578685198d6d8a62da0cfece274f282e397d97ed4f80b70433db17b9780d6cbd719bc630bfd4d88fe67acb8cc50b768b35bd61e25fc5f3c8db1337cb349013f71550e51ba6126faeae5b5e8aacfcd969fd6c15f5391ad05de20e751da5b9567edf4ee426570130b70141cc9e019ca5ff51d704b6c0674ecb52e77e174a1a399a0859ef1acd87e" assertEquals(expectedKeyEnc, Hex.encodeHexString(encodedKey)) } @@ -25,7 +25,7 @@ class BlobTest { @Test fun testEncodeKey8192() { val keyStr = "2d2d2d2d2d424547494e205253412050524956415445204b45592d2d2d2d2d0a4d4949534b67494241414b4342414541304433542b644953736d43486d37393777735830765666715557444a2f336d7644596f7a6c43616244686e474c6c53450a7041516266315a3854732b4f4d34705652484f4a554a4c305765624e646d5050476a737957517a367a5a4539366c515a4c3361764345587159565152363656350a3377644b2f6f68614d53526e4779454d4272716b56566246336743722b2f6972784433594b2b566f774f32574b732f3647724d647154413859354354462f4a650a707477735367354d4d6a723655614b3471446372656a33686b6742564776525633636a31736e4b3642723848755964466e7047475453306437554a6c4846676c0a74724748552f43424f393233686b48674a6157456a4330676953476a684b4b744c7a72566370445632792f6c57515039542f5434646a454149614871512b2b500a53644f535236707349475236685667536967743748436e45376e573731312f7266563555723945695670423034306d44496d4b5a6379382f2f544d6e5879644e0a314b595456642f33346664707a4d7053773569626c457262774f4c585654556d4f7a74596e706c343166654853762f6a506573487374506c666b6c494632766f0a475a456f6866397363517663754d3777454266432f615441394b33397a4d6d6b42626376535a6a4c79686d63535a574d50504f5a7949636c337a5935335168570a51432f61626d49634266493153342b72376d433469324a6e2b2b6f457675474e564772325359325a305a5a7858474c3148492f3038442f332b5463756d72636e0a34596a504b2f444d466930462b652b317834316c697075662b63782f3271524e51582f6d30325354724c59644d3665306733334b766c6e4664693262373532790a2f4f49614d777844614a76756e4d6836454d44574b4d31414862592f696f416f4b376553323648654a4c45446c6c714f342b535750333763386c4d76534557790a314769457252304863734f6a2f51775747504673656f56726f4d6941327355513049632f7467566a43546c58672b31325870556e6f754977654369384b634c2f0a6164327a4a6b6a75396842684a4c42512f32476e69764a69336c4667463447642f2f54534a36726757755846664d4b742f397a32537a33356f684558347941300a666c716c43654c496e46456f6576627a2b585439615266446536354d5a373979773354665039437256373468663152527a766544347a706933462b68635932690a4a5773483767524f5a65436d3666415835547265636433684f784a4f6641344e34727653534371364277437665625438465932355a2f564637635172485944530a696a3577366c71684d7a5848655545593930476139414b34587a6157774767657a712b52375a73303059534b7146763971594e4b645237747a33636a696a57660a39712f33523175683645514b544d5a4b6f345345436c4a6947796a4f42766d504b30396a4d465a544a763030684478616744505a426c3758704c444a352f4c6e0a31757070764c434e575759317a654a6661456c4d7971332f50714b5a4c6964463972566f41315349776b326c70645576506f7465326f466977435a6f586c775a0a4a326e636a6d5867514e7337362f38756e444a4130726a344a5071636377344d35477851376f6b62676d334634726d7a72694375763842654d53436b723272790a306d59335568706f68583477434d713047347835734555417a39465656505a4b6a786e59426d4c447a724a41522b342b4737675a736374303158444a596744640a4a5659496e465032322f63497265385672465759744862674f46644e7155695671353864653650645a472f452b7561576d455468536c527267456a54787570690a4f586667644b572f32306a317141746a4f6c714677735930393451357271554c513677507851494441514142416f4945415143686d6b6d6c68725242763432640a6659556979784b353262386174683073614a64447a36746c586d785944674a784d392f586c4f5274396f547a65446b6e6f454f356f6c752b72727834424267510a747a59696169775256585252455654575137746a7a5276614e4c2f47466b4c7439335854636370754b7779724e452f6269744c56616752627763492b485a46610a4d6b6e434f6968484d486f52746f386833464b41593934787a5341674f444d656b315747386a6867704358586d564e6e4250742b64346f44444944414741667a0a71676630334a356e6849622b38304b675a4f7a504f4b6e62764a614c36456d6c4c4862674233633432647a417737684874566d6f6659475763764c62324d49590a44564b4f3433352f73517831552f384e4448364a6a566441435a6a4c674f625848394b332f54743436445750456372504c6d443878686f633667464d2b5172300a41686b7a4b6f4259444e6b30436c6a6268644942586a6b74585536775251465a343575503265344a5a347a727a47424c722f74346c5461765a305351744c6c640a41366b4f7347682b6443574644746e736878596e6c2f7861642f79522b3361357a6d444a626f2f664a544258726c663142347266516b46744b323065744f50510a422b2b46432f726a68334d6d2f4b622f7039477a2f3275705a6441724839375a7644324c4246666a37376c466d4168714169337743526c4e2b656b755978615a0a743170425639795869673844796c64673164375838704f6e326b7972463372515544446634706137783976706e626b556c455569666f5639676e59736d646e690a71447a59427454763267364d4b7177517953586149555730594f4250624f656c6c574577784a7147595137793449665648664d306979486e65686b32745a63720a2b58617a4c6e7747652b427a347663677546684a584c7949752f2f6c414f685a74626b367231514a45557578614f4f515833777a79636545366e6b4473676d720a5035646a335a7064376653325656327679474849466e424a38384c52787265567667723651323855543237534238327a4d62376d525a545645327a65757562540a354432443158625a3077426f3657694b366552527244513248616565746b6a2f756f527936505758776e4161546d6d49727258774c71616f4a682f5531652b440a746673444c57643649784c6a66587647676c7248737274417a306f70727069785554655668675472476b394951526435727678754755596846756a56615949360a2b5155662b3333414664746e636238793943396a5a6d677838414b624a6b2b653733534c6842354a566f732b57746555376238642f4d696d356d414c6a6e4f360a5a316e2f75696d735437397353447179335853796d744b57586f2f3232556c72764743706f4575454c504d6236645346575237767772737668466e6759342f4b0a556e69746e7678626f45666c516e614951344966524c527a5a73582b73433545737177395535744874346f492b39314476334b626462634552675637334b36420a5a516743346c6b415171754658695a354149436b786a694d795a77547455394b4a37787631375875366f7977462f33417462564745545731442b336d614873440a7933444153576f6a79715a644c6a2b57477a4b5152612b737767434441594b65656b32664941584653644636337a784a3252784f4a3447696a53616f682b6d720a3448567663704461546a2b413854312b516442794d347339386775344744376b5674565147425a64576a75747948766830685776316774566d6268512f3431330a67444d4646447a48496a4c5459475965733468484c32323136396a565239735a316551787776544967334e3470443563466d307252755a5a54532b6f4a546f460a4732376142466968416f494341514479564236325a446e62785174686b2b7a49544b497a5255724a624c6f58725563414e63534866614e37696e4638374f76610a7a6537656a5439444e5345686274665a464a31473664694f596f53772b324d7a4658763067456b4c4b593064455479644b67484575366e5671356569764d67760a4434686339596b4a4d4844536c6d763246446b704c33415843416d6e5739724b702b64647474425a45436e6d6c504570484c6f6a367867427733704e613158730a49634c56666475674838364865786a366f306f4b6759666371725838555548745549322f585171674672496a386b736a6631664656574a524a46576d425871700a6e4d45735961727a4154654d316b512f6b446554315a55706f4750517430322f5871585434423541334154694574704d32752b6c343878746f675757673252790a47396c3933385374416d68556957316d37476e4b453645494676515938355776627a784f52304a5956555372374d72617346366e6e516c6859784675494a6f4a0a32682f4b4a51616f354743547647342b4774624a4a6d3463326e795a67777968697a4d7364677364636c7337396158694d6b725a5a6b616d4c56555a574f74450a3370412f6f42757a32716e4f3948776a62483148474f636371305458666d70465363455633435147594a646e6f3646793763626d7570614c34553961675134650a772b79674c31386e713548562b2b4c5374466e567267733559696a6a736b665264453947554d56446835704373643959323346796d616164344f2f32535243430a596b53737948354f7679444f4c706f79554a366736512b343548716d2f336c4734596a4e707a4655694d636e70372b33785533357143304c4b387845666565690a4d73316d54564569484e49703678482f547152645837335744372b59754b5a534c496652473764677269725536772b6d686876784435317548514b43416745410a322f316d42435235716d332f304c742b2b52516265794533746977343055657951717563472f2b567659373773534c6b492f4c78386977526c797758634c426e0a2b4134547667756b6d4164577a4373386e64674b4e7850412b67666f687642734d4f474e394b4f4231556735767667324a326b6949363476775943777a68645a0a4e5455556d4c2b474d464855715373575967366937694246635a6d7a6e72345732543362427879544d5a6b69374a53744238366533354b58727a63322f572f620a2b2f70355532484353617a444849356d4d7975436c486336476d5553564a3766374c486a4c39346a76694e716f627030566a3630337453634849536d4e725a770a544261766b765a475958736f574b767161766b376a424239517a61424c2b756e614652736c67356a5461694b6e49536a3434557331666a464b7538347869664c0a6e4a61457a6a445074375042786b6f374c5067455937774633396e4d3956706f65744937627752364e77444c535838555539374d47642b48592b4d4f315769310a7064324c61707772782f454b374f787a33333556524b344a6530615a6e61346a32547951644d4a616339667347505876345a734c66444c6a2f7744366c316a2b0a6c4c4c62427633496d64536a33324c4262687367463469434765584f384870504f2b512f68395856736e593532556d3258644e4d6e30335043476d365a76744d0a37445869532b6c50463930486a6f6c4a56485a54424e74645652724c7235337a4c7557456671543446654b72446178647469586b784c6a72422b352f565975370a6e74796b30315a513633564e664577533169726d4b6c392b715a6b54486b33484856396a4e5635527a575669776d4a493757707231597a42776d634b4342314f0a6f4755414444733851706e6b437a30786b4d56745977486a39714b5a6c716662487a72464455556346386b43676749416459765563676a662f2f6a75386d41380a3556513341635045365476796350572b6b5232447657313256634473462f73633155413764487a7a695068476e3938536d4e786c426a62387375536246505a380a5168565430574242446b6354696c774947507839617837553353366c47573256645336467151483566526d67514b5a79724356584c4f457a384267594272534a0a78752f335451415778483051746962646247486738506469353867596c574652686e394238536c6831615259484750623141684e4c4264302f6464592b3547320a397853794458646d5a67316355412b42337a41774e5371627a46786870327a552b563175587362706b344b746e595636435a4d39516c7243526a546b39694e550a645658462f716169526a667a726d3453736d4570436b4562737270374632325931626b6f6f4f52676c4d4f734e41574e716656587734774e2b7379586a31726f0a36765a3850455259724679414f52316473514d4968796d6e6d54506a4370614a34656d4b7268575479323073593731746848616b5a574a633232596f4e70625a0a453674674956734a50546c78672f342b667943434b6a3577577239326e687342314b425a50474f2f7a4668764d6c4a70765130744838573270624e3261306d490a3578394671414c6d2f716a774348665a49745377504d2b5a6f7a53687433634f6b4748646344354b58415866636673444a633453485a4b56497a71344e75734e0a353034522f6a76443147503873676c7947376f6d703735636b677a416d616b4c64784f503248685176495839746358705369724e4a36536c3262774b75754d460a77786f33722f6f2f3959393765344c6c66704559703965714d6463472b4e705239393349774b30556841575339483577646e57425355486435653478744455740a69494c4e52754f34366737522f4149687a3163535372615757516b4367674942414d6868505035433979743950496d316230655477434263746e465351494b6f0a4b734139726c6c3261622b624d4c6b396a63384d364d4c737a7930437457736f30397348663459593974696676726b4548526574684568387a736377557559750a736d326e31665469786b30756c364c5356676c35347558624d4a6179454e6e3450494b526b65773863413874536d613433343937773337686d442b4d674362310a414c7a7163636f3968666d6b676b4936666f316738436533554545434b7932594b536d5245646759634b394a46514f36315736416b46574a634478416d667a490a4a6a466b4b7773623754537737397a57694564536f4d396a6d377343504b41546436426d2f5a41416b5555547545466b666f626e39417831724a4e2f587862320a4d4b754155745176304e595930674556644736326a4974754b4c4964366e6e63483850472b7273526a504c495970577159644a704b783570556e522b34416b510a5336437352415377634634506442764444424946473658706a466f347050645168447a4c32735446386238535753424c6c4a516262374736554e7167435361750a537573434670617a7655354e66446d554d7563746f623245595661535871396a47616a366254556d447758487757696c66496b3958664c786e59665859724a360a786864497058476d4868754c517441674b324f314a744c6f50633973397150382f536b665037786a6a4736784873502f57764c37514531705073395a4d2f55490a4330314a4e484669394c4b436e386f356d625a6a4e386a556f77693766664b2b3736775a5547314c377a4d35797457514f59776f3054514266633866706d46770a2b5242524a58326b4a79444f32374578637a6f474f4b6a77714544614f444942392b397a63434b304267536f526962536d345a42766f787a57574436354b6c730a786450685a55486346475735416f494341514338694732376144386152557439344f656b363667464f4a78383451565a6568575071745a6a577956656e4475630a543864696e6b386f656a476a634b32554a755144613833617a7639306f63567145306e30726f6e5979737a74394962316a6c59432b434b3141723954594746670a5755354f57454479437a437071572f772f61473638553871684b6d304d766b4c4a522b47366576616e3954774568464556416d3369576c6c4e587339783239730a42756377794d4d4332337a73696d78596c53376441344474797656412b7a4c316f6d4c7053574862552f71747549334856314e654a7a73792b6743346d7750680a6a353274646c3636396679574c7a487a42524c65713664564f65646a6e436f2b6a6c5533644c323044456b39536157303844314350755a656b56316a56504d770a4a6f614463495268344b4c74513042595a37554a6546555473783143532f2b55717a715953504f69353761356b76723059385977526e534238644856467474580a4a5476383377545158485046534267666e484e65376c7352546649516675496b723262706955376838355551374c7371634936594861433037555263734746460a46724c5747683931717a416431646953486c6132526e59336e385050754d6e436b67754e684c557259646d794d6f6c374666574661396c77706c7375547a42710a4236796a3869616945334c4c2b512f65756c4a375336515066414932625530554a4f323359346b6f656f4969624545444d534351364b595a324e436c525252540a67613566533159666b44464563485551312f4b496b64594847424b426a6f4b4745787a69382b436769537953565359445a6c3677494f684c6a48324f5a336f6c0a6c64504e37694e41486972727867397638514f364f516c704c556b354c68702f3164536c5a36737933556a4671766178337477365a6a724c3838595035673d3d0a2d2d2d2d2d454e44205253412050524956415445204b45592d2d2d2d2d0a" - val encodedKey = Blob.encodePubKey(Algorithms.get("SHA256_RSA8192")!!, Hex.decodeHex(keyStr)) + val encodedKey = AuxBlob.encodePubKey(Algorithms.get("SHA256_RSA8192")!!, Hex.decodeHex(keyStr)) val expectedKeyEnc = "00002000ef8168f3d03dd3f9d212b260879bbf7bc2c5f4bd57ea5160c9ff79af0d8a3394269b0e19c62e5484a4041b7f567c4ecf8e338a554473895092f459e6cd7663cf1a3b32590cfacd913dea54192f76af0845ea615411eba579df074afe885a3124671b210c06baa45556c5de00abfbf8abc43dd82be568c0ed962acffa1ab31da9303c63909317f25ea6dc2c4a0e4c323afa51a2b8a8372b7a3de19200551af455ddc8f5b272ba06bf07b987459e91864d2d1ded42651c5825b6b18753f0813bddb78641e025a5848c2d208921a384a2ad2f3ad57290d5db2fe55903fd4ff4f876310021a1ea43ef8f49d39247aa6c20647a8558128a0b7b1c29c4ee75bbd75feb7d5e54afd122569074e34983226299732f3ffd33275f274dd4a61355dff7e1f769ccca52c3989b944adbc0e2d75535263b3b589e9978d5f7874affe33deb07b2d3e57e4948176be819912885ff6c710bdcb8cef01017c2fda4c0f4adfdccc9a405b72f4998cbca199c49958c3cf399c88725df3639dd0856402fda6e621c05f2354b8fabee60b88b6267fbea04bee18d546af6498d99d196715c62f51c8ff4f03ff7f9372e9ab727e188cf2bf0cc162d05f9efb5c78d658a9b9ff9cc7fdaa44d417fe6d36493acb61d33a7b4837dcabe59c5762d9bef9db2fce21a330c43689bee9cc87a10c0d628cd401db63f8a80282bb792dba1de24b103965a8ee3e4963f7edcf2532f4845b2d46884ad1d0772c3a3fd0c1618f16c7a856ba0c880dac510d0873fb6056309395783ed765e9527a2e2307828bc29c2ff69ddb32648eef6106124b050ff61a78af262de516017819dfff4d227aae05ae5c57cc2adffdcf64b3df9a21117e320347e5aa509e2c89c51287af6f3f974fd6917c37bae4c67bf72c374df3fd0ab57be217f5451cef783e33a62dc5fa1718da2256b07ee044e65e0a6e9f017e53ade71dde13b124e7c0e0de2bbd2482aba0700af79b4fc158db967f545edc42b1d80d28a3e70ea5aa13335c7794118f7419af402b85f3696c0681eceaf91ed9b34d1848aa85bfda9834a751eedcf77238a359ff6aff7475ba1e8440a4cc64aa384840a52621b28ce06f98f2b4f6330565326fd34843c5a8033d9065ed7a4b0c9e7f2e7d6ea69bcb08d596635cde25f68494ccaadff3ea2992e2745f6b568035488c24da5a5d52f3e8b5eda8162c026685e5c192769dc8e65e040db3bebff2e9c3240d2b8f824fa9c730e0ce46c50ee891b826dc5e2b9b3ae20aebfc05e3120a4af6af2d26637521a68857e3008cab41b8c79b04500cfd15554f64a8f19d80662c3ceb24047ee3e1bb819b1cb74d570c96200dd2556089c53f6dbf708adef15ac5598b476e038574da94895ab9f1d7ba3dd646fc4fae6969844e14a546b8048d3c6ea623977e074a5bfdb48f5a80b633a5a85c2c634f78439aea50b43ac0fc51af9112e97f272e5448cc3110fce7a4081ea0613cba5637de45982ff7144d57cb6401e5186d39755af273947e526d5bb5bd4390562cfb8f28039c14be94986fb4f79a49bb97a30d2f252a8569e121993af3c57a60e98cb07dfedbdeffc887ada8d098ac4deef25fee3b33ce90be9d27ae66602da89b4b931a08ae776e7ecac8ccb30c02b72208b87f05b03b9cf83f66e4be8344fd4c4ddd08194ad7a0a3a6d6ec21058e6de8bf9c8691cb7994748e71989387340a5ecd3cd0f506b6686522673e9d2770cd13db18217cd261f706efcc0f4b3842982e4c8d333b3e0ae9348b91e15843e17371426b8294964f2e74cd1c2e08a575b66a79494d98f1db480edc741f689a3678985c379609bac92b0c192dfe5ea44fc284cf67297220888210d9ebd2d100ffc8bcb13636aad6263b2a735e3d006f309100dc391297ce7253e6df1885eefee0e7035ef318053d7177cd4acc5ec92031a1fd66def3404c4adfff0f12194a093fad509a0a6c7422011d0b0c40a6da8601fc71e48ad2544ebc652499ed591e6eb89b074fca8a920b8bbb568abfcc0af73fa4f3256cb30609cdbd11307db751d0835e09cfa1c747a58ed2919d4bd6a9a8eb2c1fb92e003bb090de173dc704b387f41b7285f95294d1ce756b269e9e3814ccb3f80efe0d7eac6d0cfe2cc51524aa4c984476d74fd34f3e4ff126cf01523a1deda0e5a0772f0e61bed8dc3615d547d8113d23d8f0bfc0097ae88258e6478cee389fbb6bf4ea68d083a938d7f781ce370c03cbd10a053e7d6a37526f610220eb684805e62204f210a790e7009cf71fb036851f5fcdb86bf640c94e0c0a412a66abf5a7f205d515b6c85d65b7da2f7e40dcf3d0567d2d851f02942a0f9a67e7f9f4427460de297d17219b940ddcfaf8310d6e906831be39c2cf1d527f2ffabd95dfe145c8f0adecfc4b167f38ae03fd8d2fe4d42bd8f2ae20f59a3688153a8b2c75ad360bd9a900d810d5ce17a5be35436cd15d6b2b881b56fd97277e4b2cd82f25df35286ec724a125280a036bcb602bd0038c5ea3026547462a06e4a45729ca0784e31d1103b7f991eaa89d7121cfd67510fb18d4d5b03068884c592d6bad56815d4eb4cffab1feb7ea27dd4e28c62db6d18f8d838b48553cd737611657463349f70cdc260ab0ad9bf163658eacc78982a274f85aadb0becf92588ecd53fc5e229bb1fe870a5f18c5c66bd154d052b2e2663004c0d6beacfcb55094ffb1898b7dbe3c9653815da4c11d53ac018b98fbb36fa61197de15258dc4614807c83c02f15420527508e63f8327b4c9862291810ff453b9badb3d7620d8c1aab8b5d50d6af59fc181161a5b1039090062f0c8995822be2df158447253b8abf913225c1bdef3e9a54a0589c1f69580e2565b28c75e9c4c8d738504ee8e08de414c64d8cee4864ecf9a60948515cc07680" assertEquals(expectedKeyEnc, Hex.encodeHexString(encodedKey)) } diff --git a/build.gradle b/build.gradle index da69264..f0ec782 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,5 @@ apply plugin: 'java' -apply plugin: 'groovy' - subprojects { tasks.withType(JavaCompile) { //options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" @@ -11,6 +9,14 @@ subprojects { // ---------------------------------------------------------------------------- // global // ---------------------------------------------------------------------------- + +if (Float.parseFloat(gradle.gradleVersion) < 5.0) { + logger.error("ERROR: Gradle Version MUST >= 5.0, current is {}", gradle.gradleVersion) + throw new RuntimeException("ERROR: Gradle Version") +} else { + logger.info("Gradle Version {}", gradle.gradleVersion) +} + def workdir = 'build/unzip_boot' project.ext.rootWorkDir = new File(workdir).getAbsolutePath() String activeImg = "boot.img" @@ -33,7 +39,7 @@ project.ext.mkbootimgBin = new File("src/mkbootimg/mkbootimg").getAbsolutePath() project.ext.mkbootfsBin = new File("mkbootfs/build/exe/mkbootfs/mkbootfs").getAbsolutePath() project.ext.avbtool = new File("avb/avbtool").getAbsolutePath() project.ext.bootSigner = new File("boot_signer/build/libs/boot_signer.jar").getAbsolutePath() -println("Active image target: " + activeImg) +logger.warn("Active image target: " + activeImg) // ---------------------------------------------------------------------------- // tasks @@ -168,12 +174,3 @@ task rr { rebootRecovery() } } - -boolean inArray(String file, String[] inArray) { - for (String item : inArray) { - if (item.equals(file)) { - return true; - } - } - return false; -} diff --git a/doc/layout.md b/doc/layout.md index fe398f1..a6bcb01 100644 --- a/doc/layout.md +++ b/doc/layout.md @@ -15,81 +15,93 @@ ### 1. header part item size in bytes position - +----------------------------------------------------------+ --> 0 - | | 8 | - |--------------------------------+-------------------------| --> 8 - | | 4 | - |--------------------------------+-------------------------| --> 12 - | | 4 | - |--------------------------------+-------------------------| --> 16 (0x10) - | | 4 | - |--------------------------------+-------------------------| --> 20 - | | 4 | - |--------------------------------+-------------------------| --> 24 - | | 4 | - |--------------------------------+-------------------------| --> 28 - | | 4 | - |--------------------------------+-------------------------| --> 32 (0x20) - | | 4 | - |--------------------------------+-------------------------| --> 36 - | | 4 | - |--------------------------------+-------------------------| --> 40 - |
| 4 | - |--------------------------------+-------------------------| --> 44 - | | 4 | - |--------------------------------+-------------------------| --> 48 (0x30) - | | 16 | - |--------------------------------+-------------------------| --> 64 (0x40) - | | 512 | - |--------------------------------+-------------------------| --> 576 (0x240) - | | 32 | - |--------------------------------+-------------------------| --> 608 (0x260) - | | 1024 | - |--------------------------------+-------------------------| --> 1632 (0x660) - | | 4 | - |--------------------------------+-------------------------| --> 1636 - | | 8 | - |--------------------------------+-------------------------| --> 1644 - |
| 4 | - |--------------------------------+-------------------------| --> 1648 (0x670) - | | min(n * page_zie - 1648)| - +----------------------------------------------------------+ --> pagesize + +-----------------------------------------------------------+ --> 0 + | | 8 | + |--------------------------------+--------------------------| --> 8 + | | 4 | + |--------------------------------+--------------------------| --> 12 + | | 4 | + |--------------------------------+--------------------------| --> 16 (0x10) + | | 4 | + |--------------------------------+--------------------------| --> 20 + | | 4 | + |--------------------------------+--------------------------| --> 24 + | | 4 | + |--------------------------------+--------------------------| --> 28 + | | 4 | + |--------------------------------+--------------------------| --> 32 (0x20) + | | 4 | + |--------------------------------+--------------------------| --> 36 + | | 4 | + |--------------------------------+--------------------------| --> 40 + |
| 4 (value in [0,1,2]) | + |--------------------------------+--------------------------| --> 44 + | | 4 | + |--------------------------------+--------------------------| --> 48 (0x30) + | | 16 | + |--------------------------------+--------------------------| --> 64 (0x40) + | | 512 | + |--------------------------------+--------------------------| --> 576 (0x240) + | | 32 | + |--------------------------------+--------------------------| --> 608 (0x260) + | | 1024 | + |--------------------------------+--------------------------| --> 1632 (0x660) + | [v1] | 4 | + |--------------------------------+--------------------------| --> 1636 + | [v1] | 8 | + |--------------------------------+--------------------------| --> 1644 + |
[v1] | 4 (v1: value=1648) | + | | (v2: value=1660) | + |--------------------------------+--------------------------| --> 1648 (0x670) + | [v2] | 4 | + |--------------------------------+--------------------------| --> 1652 + | [v2] | 8 | + |--------------------------------+--------------------------| --> 1660 (0x67c) + | | min(n * page_size | + | | - header_size) | + +--------------------------------+--------------------------+ --> pagesize ### 2. data part - +----------------------------------------------------------+ --> pagesize - | | kernel length | - |--------------------------------+-------------------------| - | | min(n * page_zie - len)| - +----------------------------------------------------------+ - - +--------------------------------+-------------------------+ - | | ramdisk length | - |--------------------------------+-------------------------| - | | min(n * page_zie - len)| - +----------------------------------------------------------+ - - +--------------------------------+-------------------------+ - | | second bootloader length| - |--------------------------------+-------------------------| - | | min(n * page_zie - len)| - +----------------------------------------------------------+ - - +--------------------------------+-------------------------+ - | | recovery dtbo length | - |--------------------------------+-------------------------| - | | min(n * page_zie - len)| - +----------------------------------------------------------+ --> end of data part + +-----------------------------------------------------------+ --> pagesize + | | kernel length | + |--------------------------------+--------------------------| + | | min(n * page_size - len) | + +-----------------------------------------------------------+ + + +-----------------------------------------------------------+ + | | ramdisk length | + |--------------------------------+--------------------------| + | | min(n * page_size - len) | + +-----------------------------------------------------------+ + + +-----------------------------------------------------------+ + | | second bootloader length | + |--------------------------------+--------------------------| + | | min(n * page_size - len) | + +-----------------------------------------------------------+ + + +-----------------------------------------------------------+ + | [v1] | recovery dtbo length | + |--------------------------------+--------------------------| + | [v1] | min(n * page_size - len) | + +-----------------------------------------------------------+ + + +-----------------------------------------------------------+ + | [v2] | dtb length | + |--------------------------------+--------------------------| + | [v2] | min(n * page_size - len) | + +-----------------------------------------------------------+ --> end of data part ### 3. signature part #### 3.1 Boot Image Signature (VBoot 1.0) - +--------------------------------+-------------------------+ --> end of data part - | | signature length | - |--------------------------------+-------------------------| - | | defined by boot_signer | - +--------------------------------+-------------------------+ + +--------------------------------+--------------------------+ --> end of data part + | | signature length | + |--------------------------------+--------------------------+ + | | defined by boot_signer | + +--------------------------------+--------------------------+ #### 3.2 AVB Footer (VBoot 2.0) diff --git a/integrationTest.py b/integrationTest.py new file mode 100755 index 0000000..dd11cf8 --- /dev/null +++ b/integrationTest.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 + +import shutil, os.path, json, subprocess, hashlib, glob +import unittest + +def hashFile(fileName): + hasher = hashlib.md5() + with open(fileName, 'rb') as afile: + buf = afile.read() + hasher.update(buf) + return hasher.hexdigest() + +def deleteIfExists(inFile): + if os.path.isfile(inFile): + os.remove(inFile) + +def verifySingleJson(inResourceDir, inImageDir, jsonFile): + print(jsonFile) + resDir = inResourceDir + imgDir = inImageDir + verifyItems = json.load(open(jsonFile)) + for k, v in verifyItems["copy"].items(): + shutil.copyfile(os.path.join(resDir, imgDir, k), v) + + subprocess.check_call("gradle unpack", shell = True) + subprocess.check_call("gradle pack", shell = True) + + for k, v in verifyItems["hash"].items(): + print("%s : %s" % (k, v)) + unittest.TestCase().assertEqual(hashFile(k), v) + + shutil.rmtree("build") + deleteIfExists("boot.img") + deleteIfExists("boot.img.clear") + deleteIfExists("boot.img.google") + deleteIfExists("boot.img.signed") + deleteIfExists("recovery.img") + deleteIfExists("recovery.img.clear") + deleteIfExists("recovery.img.google") + deleteIfExists("recovery.img.signed") + deleteIfExists("vbmeta.img") + deleteIfExists("vbmeta.img.signed") + +def verifySingleDir(inResourceDir, inImageDir): + resDir = inResourceDir + imgDir = inImageDir + print("enter %s ..." % os.path.join(resDir, imgDir)) + jsonFiles = glob.glob(os.path.join(resDir, imgDir) + "/*.json") + for jsonFile in jsonFiles: + verifySingleJson(inResourceDir, inImageDir, jsonFile) + +# 5.0 +verifySingleDir("boot_image_res", "5.0_fugu_lrx21m") +# 6.0 +verifySingleDir("boot_image_res", "6.0.0_bullhead_mda89e") +# 7.0 special boot +subprocess.check_call("dd if=boot_image_res/7.1.1_volantis_n9f27m/boot.img of=boot.img bs=256 skip=1", shell = True) +verifySingleJson("boot_image_res", "7.1.1_volantis_n9f27m", "boot_image_res/7.1.1_volantis_n9f27m/boot.json") +# 7.0 special recovery +subprocess.check_call("dd if=boot_image_res/7.1.1_volantis_n9f27m/recovery.img of=recovery.img bs=256 skip=1", shell = True) +verifySingleJson("boot_image_res", "7.1.1_volantis_n9f27m", "boot_image_res/7.1.1_volantis_n9f27m/recovery.json") +# 8.0 +verifySingleDir("boot_image_res", "8.0.0_fugu_opr2.170623.027") +# 9.0 + avb +subprocess.check_call("tar xf boot_image_res/9.0.0_blueline_pq1a.181105.017.a1/boot.img.tar.gz", shell = True) +verifySingleJson("boot_image_res", "9.0.0_blueline_pq1a.181105.017.a1", "boot_image_res/9.0.0_blueline_pq1a.181105.017.a1/boot.json") +verifySingleJson("boot_image_res", "9.0.0_blueline_pq1a.181105.017.a1", "boot_image_res/9.0.0_blueline_pq1a.181105.017.a1/vbmeta.json") diff --git a/src/mkbootimg/mkbootimg b/src/mkbootimg/mkbootimg index ac20d05..92b11a5 100755 --- a/src/mkbootimg/mkbootimg +++ b/src/mkbootimg/mkbootimg @@ -45,8 +45,30 @@ def pad_file(f, padding): f.write(pack(str(pad) + 'x')) +def get_number_of_pages(image_size, page_size): + """calculates the number of pages required for the image""" + return (image_size + page_size - 1) / page_size + + +def get_recovery_dtbo_offset(args): + """calculates the offset of recovery_dtbo image in the boot image""" + num_header_pages = 1 # header occupies a page + num_kernel_pages = get_number_of_pages(filesize(args.kernel), args.pagesize) + num_ramdisk_pages = get_number_of_pages(filesize(args.ramdisk), args.pagesize) + num_second_pages = get_number_of_pages(filesize(args.second), args.pagesize) + dtbo_offset = args.pagesize * (num_header_pages + num_kernel_pages + + num_ramdisk_pages + num_second_pages) + return dtbo_offset + + def write_header(args): + BOOT_IMAGE_HEADER_V1_SIZE = 1648 + BOOT_IMAGE_HEADER_V2_SIZE = 1660 BOOT_MAGIC = 'ANDROID!'.encode() + + if (args.header_version > 2): + raise ValueError('Boot header version %d not supported' % args.header_version) + args.output.write(pack('8s', BOOT_MAGIC)) args.output.write(pack('10I', filesize(args.kernel), # size in bytes @@ -69,6 +91,8 @@ def write_header(args): if args.header_version > 0: update_sha(sha, args.recovery_dtbo) + if args.header_version > 1: + update_sha(sha, args.dtb) img_id = pack('32s', sha.digest()) @@ -76,10 +100,21 @@ def write_header(args): args.output.write(pack('1024s', args.cmdline[512:].encode())) if args.header_version > 0: - args.output.write(pack('I', filesize(args.recovery_dtbo))) # size in bytes - args.output.write(pack('Q', args.base + args.recovery_dtbo_offset)) # physical load addr - args.output.write(pack('I', args.output.tell() + 4)) # size of boot header - + args.output.write(pack('I', filesize(args.recovery_dtbo))) # size in bytes + if args.recovery_dtbo: + args.output.write(pack('Q', get_recovery_dtbo_offset(args))) # recovery dtbo offset + else: + args.output.write(pack('Q', 0)) # Will be set to 0 for devices without a recovery dtbo + + # Populate boot image header size for header versions 1 and 2. + if args.header_version == 1: + args.output.write(pack('I', BOOT_IMAGE_HEADER_V1_SIZE)) + elif args.header_version == 2: + args.output.write(pack('I', BOOT_IMAGE_HEADER_V2_SIZE)) + + if args.header_version > 1: + args.output.write(pack('I', filesize(args.dtb))) # size in bytes + args.output.write(pack('Q', args.base + args.dtb_offset)) # dtb physical load address pad_file(args.output, args.pagesize) return img_id @@ -142,7 +177,11 @@ def parse_cmdline(): required=True) parser.add_argument('--ramdisk', help='path to the ramdisk', type=FileType('rb')) parser.add_argument('--second', help='path to the 2nd bootloader', type=FileType('rb')) - parser.add_argument('--recovery_dtbo', help='path to the recovery DTBO', type=FileType('rb')) + parser.add_argument('--dtb', help='path to dtb', type=FileType('rb')) + recovery_dtbo_group = parser.add_mutually_exclusive_group() + recovery_dtbo_group.add_argument('--recovery_dtbo', help='path to the recovery DTBO', type=FileType('rb')) + recovery_dtbo_group.add_argument('--recovery_acpio', help='path to the recovery ACPIO', + type=FileType('rb'), metavar='RECOVERY_ACPIO', dest='recovery_dtbo') parser.add_argument('--cmdline', help='extra arguments to be passed on the ' 'kernel command line', default='', action=ValidateStrLenAction, maxlen=1536) parser.add_argument('--base', help='base address', type=parse_int, default=0x10000000) @@ -150,8 +189,8 @@ def parse_cmdline(): parser.add_argument('--ramdisk_offset', help='ramdisk offset', type=parse_int, default=0x01000000) parser.add_argument('--second_offset', help='2nd bootloader offset', type=parse_int, default=0x00f00000) - parser.add_argument('--recovery_dtbo_offset', help='recovery dtbo offset', type=parse_int, - default=0x0f000000) + parser.add_argument('--dtb_offset', help='dtb offset', type=parse_int, default=0x01f00000) + parser.add_argument('--os_version', help='operating system version', type=parse_os_version, default=0) parser.add_argument('--os_patch_level', help='operating system patch level', @@ -176,6 +215,8 @@ def write_data(args): if args.header_version > 0: write_padded_file(args.output, args.recovery_dtbo, args.pagesize) + if args.header_version > 1: + write_padded_file(args.output, args.dtb, args.pagesize) def main(): args = parse_cmdline()