diff --git a/Makefile b/Makefile deleted file mode 100644 index 09675a4..0000000 --- a/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -t: - external/extract_kernel.py \ - --input build/unzip_boot/kernel \ - --output-configs kernel_configs.txt \ - --output-version kernel_version.txt - -t2: - rm -fr dtbo - mkdir dtbo - external/mkdtboimg.py \ - dump dtbo.img \ - --dtb dtbo/dtb.dump \ - --output dtbo/header.dump diff --git a/README.md b/README.md index 56cc7b0..123df56 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![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) -This tool focuses on editing Android boot.img(also recovery.img, recovery-two-step.img and vbmeta.img). +This tool focuses on editing Android boot.img(also recovery.img, and vbmeta.img). ## 1. Prerequisite #### 1.1 Host OS requirement: @@ -12,13 +12,13 @@ Also need python 2.x and jdk 8. #### 1.2 Target Android requirement: -(1) Target boot.img MUST follows AOSP verified boot flow, either [Boot image signature](https://source.android.com/security/verifiedboot/verified-boot#signature_format) in VBoot 1.0 or [AVB HASH footer](https://android.googlesource.com/platform/external/avb/+/master/README.md#The-VBMeta-struct) in VBoot 2.0. +(1) Target boot.img MUST follows AOSP verified boot flow, either [Boot image signature](https://source.android.com/security/verifiedboot/verified-boot#signature_format) in VBoot 1.0 or [AVB HASH footer](https://android.googlesource.com/platform/external/avb/+/master/README.md#The-VBMeta-struct) (a.k.a. AVB) in VBoot 2.0. Supported images: - boot.img - - recovery.img - - recovery-two-step.img - - vbmeta.img + - recovery.img (also recovery-two-step.img) + - vbmeta.img (also vbmeta\_system.img, vbmeta\_vendor.img etc.) + - dtbo.img (only 'unpack' is supported) (2) These utilities are known to work for Nexus/Pixel boot.img for the following Android releases: diff --git a/avb/avbtool b/avb/avbtool index b7af231..1762d38 100755 --- a/avb/avbtool +++ b/avb/avbtool @@ -1121,7 +1121,7 @@ class AvbDescriptor(object): return bytearray(ret) def verify(self, image_dir, image_ext, expected_chain_partitions_map, - image_containing_descriptor): + image_containing_descriptor, accept_zeroed_hashtree): """Verifies contents of the descriptor - used in verify_image sub-command. Arguments: @@ -1130,6 +1130,7 @@ class AvbDescriptor(object): 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. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is zeroed out. Returns: True if the descriptor verifies, False otherwise. @@ -1207,7 +1208,7 @@ class AvbPropertyDescriptor(AvbDescriptor): return bytearray(ret) def verify(self, image_dir, image_ext, expected_chain_partitions_map, - image_containing_descriptor): + image_containing_descriptor, accept_zeroed_hashtree): """Verifies contents of the descriptor - used in verify_image sub-command. Arguments: @@ -1216,6 +1217,7 @@ class AvbPropertyDescriptor(AvbDescriptor): 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. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is zeroed out. Returns: True if the descriptor verifies, False otherwise. @@ -1369,7 +1371,7 @@ class AvbHashtreeDescriptor(AvbDescriptor): return bytearray(ret) def verify(self, image_dir, image_ext, expected_chain_partitions_map, - image_containing_descriptor): + image_containing_descriptor, accept_zeroed_hashtree): """Verifies contents of the descriptor - used in verify_image sub-command. Arguments: @@ -1378,6 +1380,7 @@ class AvbHashtreeDescriptor(AvbDescriptor): 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. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is zeroed out. Returns: True if the descriptor verifies, False otherwise. @@ -1406,17 +1409,22 @@ class AvbHashtreeDescriptor(AvbDescriptor): # ... also check that the on-disk hashtree matches image.seek(self.tree_offset) hash_tree_ondisk = image.read(self.tree_size) - if hash_tree != hash_tree_ondisk: - sys.stderr.write('hashtree of {} contains invalid data\n'. + is_zeroed = (hash_tree_ondisk[0:8] == 'ZeRoHaSH') + if is_zeroed and accept_zeroed_hashtree: + print ('{}: skipping verification since hashtree is zeroed and --accept_zeroed_hashtree was given' + .format(self.partition_name)) + else: + if hash_tree != hash_tree_ondisk: + sys.stderr.write('hashtree of {} contains invalid data\n'. format(image_filename)) - return False + return False + print ('{}: Successfully verified {} hashtree of {} for image of {} bytes' + .format(self.partition_name, self.hash_algorithm, image.filename, + self.image_size)) # TODO: we could also verify that the FEC stored in the image is # correct but this a) currently requires the 'fec' binary; and b) # 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, - self.image_size)) return True @@ -1526,7 +1534,7 @@ class AvbHashDescriptor(AvbDescriptor): return bytearray(ret) def verify(self, image_dir, image_ext, expected_chain_partitions_map, - image_containing_descriptor): + image_containing_descriptor, accept_zeroed_hashtree): """Verifies contents of the descriptor - used in verify_image sub-command. Arguments: @@ -1535,6 +1543,7 @@ class AvbHashDescriptor(AvbDescriptor): 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. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is zeroed out. Returns: True if the descriptor verifies, False otherwise. @@ -1636,7 +1645,7 @@ class AvbKernelCmdlineDescriptor(AvbDescriptor): return bytearray(ret) def verify(self, image_dir, image_ext, expected_chain_partitions_map, - image_containing_descriptor): + image_containing_descriptor, accept_zeroed_hashtree): """Verifies contents of the descriptor - used in verify_image sub-command. Arguments: @@ -1645,6 +1654,7 @@ class AvbKernelCmdlineDescriptor(AvbDescriptor): 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. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is zeroed out. Returns: True if the descriptor verifies, False otherwise. @@ -1739,7 +1749,7 @@ class AvbChainPartitionDescriptor(AvbDescriptor): return bytearray(ret) def verify(self, image_dir, image_ext, expected_chain_partitions_map, - image_containing_descriptor): + image_containing_descriptor, accept_zeroed_hashtree): """Verifies contents of the descriptor - used in verify_image sub-command. Arguments: @@ -1748,6 +1758,7 @@ class AvbChainPartitionDescriptor(AvbDescriptor): 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. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is zeroed out. Returns: True if the descriptor verifies, False otherwise. @@ -2086,6 +2097,63 @@ class Avb(object): # And cut... image.truncate(new_image_size) + def zero_hashtree(self, image_filename): + """Implements the 'zero_hashtree' command. + + Arguments: + image_filename: File to zero hashtree and FEC data from. + + Raises: + AvbError: If there's no footer in the image. + """ + + image = ImageHandler(image_filename) + + (footer, _, descriptors, _) = self._parse_image(image) + + if not footer: + raise AvbError('Given image does not have a footer.') + + # Search for a hashtree descriptor to figure out the location and + # size of the hashtree and FEC. + ht_desc = None + for desc in descriptors: + if isinstance(desc, AvbHashtreeDescriptor): + ht_desc = desc + break + + if not ht_desc: + raise AvbError('No hashtree descriptor was found.') + + zero_ht_start_offset = ht_desc.tree_offset + zero_ht_num_bytes = ht_desc.tree_size + zero_fec_start_offset = None + zero_fec_num_bytes = 0 + if ht_desc.fec_offset > 0: + if ht_desc.fec_offset != ht_desc.tree_offset + ht_desc.tree_size: + raise AvbError('Hash-tree and FEC data must be adjacent.') + zero_fec_start_offset = ht_desc.fec_offset + zero_fec_num_bytes = ht_desc.fec_size + zero_end_offset = zero_ht_start_offset + zero_ht_num_bytes + zero_fec_num_bytes + image.seek(zero_end_offset) + data = image.read(image.image_size - zero_end_offset) + + # Write zeroes all over hashtree and FEC, except for the first eight bytes + # where a magic marker - ZeroHaSH - is placed. Place these markers in the + # beginning of both hashtree and FEC. (That way, in the future we can add + # options to 'avbtool zero_hashtree' so as to zero out only either/or.) + # + # Applications can use these markers to detect that the hashtree and/or + # FEC needs to be recomputed. + image.truncate(zero_ht_start_offset) + data_zeroed_firstblock = 'ZeRoHaSH' + '\0'*(image.block_size - 8) + image.append_raw(data_zeroed_firstblock) + image.append_fill('\0\0\0\0', zero_ht_num_bytes - image.block_size) + if zero_fec_start_offset: + image.append_raw(data_zeroed_firstblock) + image.append_fill('\0\0\0\0', zero_fec_num_bytes - image.block_size) + image.append_raw(data) + def resize_image(self, image_filename, partition_size): """Implements the 'resize_image' command. @@ -2220,7 +2288,8 @@ class Avb(object): if num_printed == 0: o.write(' (none)\n') - def verify_image(self, image_filename, key_path, expected_chain_partitions, follow_chain_partitions): + def verify_image(self, image_filename, key_path, expected_chain_partitions, follow_chain_partitions, + accept_zeroed_hashtree): """Implements the 'verify_image' command. Arguments: @@ -2229,6 +2298,7 @@ class Avb(object): 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 + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is zeroed out. """ expected_chain_partitions_map = {} if expected_chain_partitions: @@ -2244,8 +2314,7 @@ class Avb(object): expected_chain_partitions_map[partition_name] = (rollback_index_location, pk_blob) image_dir = os.path.dirname(image_filename) - #image_ext = os.path.splitext(image_filename)[1] - image_ext = image_filename[image_filename.index('.'):] + image_ext = os.path.splitext(image_filename)[1] key_blob = None if key_path: @@ -2295,13 +2364,14 @@ class Avb(object): .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): + if not desc.verify(image_dir, image_ext, expected_chain_partitions_map, image, + accept_zeroed_hashtree): 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) + self.verify_image(chained_image_filename, key_path, None, False, accept_zeroed_hashtree) def calculate_vbmeta_digest(self, image_filename, hash_algorithm, output): @@ -4027,6 +4097,14 @@ class AvbTool(object): action='store_true') sub_parser.set_defaults(func=self.erase_footer) + sub_parser = subparsers.add_parser('zero_hashtree', + help='Zero out hashtree and FEC data.') + sub_parser.add_argument('--image', + help='Image with a footer', + type=argparse.FileType('rwb+'), + required=True) + sub_parser.set_defaults(func=self.zero_hashtree) + sub_parser = subparsers.add_parser('extract_vbmeta_image', help='Extracts vbmeta from an image with a footer.') sub_parser.add_argument('--image', @@ -4087,6 +4165,9 @@ class AvbTool(object): help=('Follows chain partitions even when not ' 'specified with the --expected_chain_partition option'), action='store_true') + sub_parser.add_argument('--accept_zeroed_hashtree', + help=('Accept images where the hashtree or FEC data is zeroed out'), + action='store_true') sub_parser.set_defaults(func=self.verify_image) sub_parser = subparsers.add_parser( @@ -4348,6 +4429,10 @@ class AvbTool(object): """Implements the 'erase_footer' sub-command.""" self.avb.erase_footer(args.image.name, args.keep_hashtree) + def zero_hashtree(self, args): + """Implements the 'zero_hashtree' sub-command.""" + self.avb.zero_hashtree(args.image.name) + def extract_vbmeta_image(self, args): """Implements the 'extract_vbmeta_image' sub-command.""" self.avb.extract_vbmeta_image(args.output, args.image.name, @@ -4369,7 +4454,8 @@ class AvbTool(object): """Implements the 'verify_image' sub-command.""" self.avb.verify_image(args.image.name, args.key, args.expected_chain_partition, - args.follow_chain_partitions) + args.follow_chain_partitions, + args.accept_zeroed_hashtree) def calculate_vbmeta_digest(self, args): """Implements the 'calculate_vbmeta_digest' sub-command.""" diff --git a/bbootimg/build.gradle b/bbootimg/build.gradle index 0c1503f..0653606 100644 --- a/bbootimg/build.gradle +++ b/bbootimg/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlinVersion = "1.3.30" + kotlinVersion = "1.3.41" } repositories { mavenCentral() @@ -11,7 +11,6 @@ buildscript { } } -apply plugin: "java" apply plugin: "kotlin" apply plugin: "application" diff --git a/bbootimg/src/main/java/cfig/io/Struct.java b/bbootimg/src/main/java/cfig/io/Struct.java deleted file mode 100644 index 4d2e4bd..0000000 --- a/bbootimg/src/main/java/cfig/io/Struct.java +++ /dev/null @@ -1,367 +0,0 @@ -package cfig.io; - -import cfig.Helper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static org.junit.Assert.assertEquals; - -public class Struct { - private static Logger log = LoggerFactory.getLogger(Struct.class); - - private ByteOrder byteOrder = ByteOrder.LITTLE_ENDIAN; - private List formats = new ArrayList<>(); - - public Struct(String formatString) { - Matcher m = Pattern.compile("(\\d*)([a-zA-Z])").matcher(formatString); - - if (formatString.startsWith(">") || formatString.startsWith("!")) { - this.byteOrder = ByteOrder.BIG_ENDIAN; - log.debug("Parsing BIG_ENDIAN format: " + formatString); - } else if (formatString.startsWith("@") || formatString.startsWith("=")) { - this.byteOrder = ByteOrder.nativeOrder(); - log.debug("Parsing native ENDIAN format: " + formatString); - } else { - log.debug("Parsing LITTLE_ENDIAN format: " + formatString); - } - - while (m.find()) { - boolean bExpand = true; - int mul = 1; - if (!m.group(1).isEmpty()) { - mul = Integer.decode(m.group(1)); - } - //item[0]: Type, item[1]: multiple - // if need to expand format items, explode it - // eg: "4L" will be exploded to "1L 1L 1L 1L" - // eg: "10x" won't be exploded, it's still "10x" - Object item[] = new Object[2]; - - switch (m.group(2)) { - case "x": {//byte 1 - item[0] = PadByte.class; - bExpand = false; - break; - } - case "b": {//byte 1 - item[0] = Byte.class; - bExpand = false; - break; - } - case "s": {//python: char 1 - item[0] = Character.class; - bExpand = false; - break; - } - case "h": {//2 - item[0] = Short.class; - break; - } - case "H": {//2 - item[0] = UnsignedShort.class; - break; - } - case "i": - case "l": {//4 - item[0] = Integer.class; - break; - } - case "I": - case "L": {//4 - item[0] = UnsignedInt.class; - break; - } - case "q": {//8 - item[0] = Long.class; - break; - } - case "Q": {//8 - item[0] = UnsignedLong.class; - break; - } - default: { - throw new IllegalArgumentException("type [" + m.group(2) + "] not supported"); - } - } - if (bExpand) { - item[1] = 1; - for (int i = 0; i < mul; i++) { - formats.add(item); - } - } else { - item[1] = mul; - formats.add(item); - } - } - } - - public Integer calcSize() { - int ret = 0; - for (Object[] format : formats) { - if (format[0] == Byte.class || format[0] == Character.class || format[0] == PadByte.class) { - ret += (int) format[1]; - continue; - } - if (format[0] == Short.class) { - ret += 2 * (int) format[1]; - continue; - } - if (format[0] == UnsignedShort.class) { - ret += 2 * (int) format[1]; - continue; - } - if (format[0] == Integer.class) { - ret += 4 * (int) format[1]; - continue; - } - if (format[0] == UnsignedInt.class) { - ret += 4 * (int) format[1]; - continue; - } - if (format[0] == Long.class || format[0] == UnsignedLong.class) { - ret += 8 * (int) format[1]; - continue; - } - throw new IllegalArgumentException("Class [" + format[0] + "] not supported"); - } - return ret; - } - - public void dump() { - log.info("--- Format ---"); - log.info("Endian: " + this.byteOrder); - for (Object[] formatItem : formats) { - log.info(formatItem[0] + ":" + formatItem[1]); - } - log.info("--- Format ---"); - } - - public List unpack(InputStream iS) throws IOException { - List ret = new ArrayList<>(); - ByteBuffer bf = ByteBuffer.allocate(32); - bf.order(this.byteOrder); - for (Object[] format : this.formats) { - //return 'null' for padding bytes - if (format[0] == PadByte.class) { - long skipped = iS.skip((Integer) format[1]); - assertEquals((long) (Integer) format[1], skipped); - ret.add(null); - continue; - } - - if (format[0] == Byte.class || format[0] == Character.class) { - byte[] data = new byte[(Integer) format[1]]; - assertEquals((int) format[1], iS.read(data)); - ret.add(data); - continue; - } - - if (format[0] == Short.class) { - byte[] data = new byte[2]; - assertEquals(2, iS.read(data)); - bf.clear(); - bf.put(data); - bf.flip(); - ret.add(bf.getShort()); - continue; - } - - if (format[0] == UnsignedShort.class) { - byte[] data = new byte[2]; - assertEquals(2, iS.read(data)); - log.debug("UnsignedShort: " + Helper.Companion.toHexString(data)); - bf.clear(); - if (this.byteOrder == ByteOrder.LITTLE_ENDIAN) { - bf.put(data); - bf.put(new byte[2]); //complete high bits with 0 - } else { - bf.put(new byte[2]); //complete high bits with 0 - bf.put(data); - } - bf.flip(); - ret.add(bf.getInt()); - continue; - } - - if (format[0] == Integer.class) { - byte[] data = new byte[4]; - assertEquals(4, iS.read(data)); - log.debug("Integer: " + Helper.Companion.toHexString(data)); - bf.clear(); - bf.put(data); - bf.flip(); - ret.add(bf.getInt()); - continue; - } - - if (format[0] == UnsignedInt.class) { - byte[] data = new byte[4]; - assertEquals(4, iS.read(data)); - bf.clear(); - log.debug("UnsignedInt: " + Helper.Companion.toHexString(data)); - if (this.byteOrder == ByteOrder.LITTLE_ENDIAN) { - bf.put(data); - bf.put(new byte[4]); //complete high bits with 0 - } else { - bf.put(new byte[4]); //complete high bits with 0 - bf.put(data); - } - bf.flip(); - ret.add(bf.getLong()); - continue; - } - - //TODO: maybe exceeds limits of Long.class ? - if (format[0] == Long.class || format[0] == UnsignedLong.class) { - byte[] data = new byte[8]; - assertEquals(8, iS.read(data)); - bf.clear(); - bf.put(data); - bf.flip(); - ret.add(bf.getLong()); - continue; - } - - throw new IllegalArgumentException("Class [" + format[0] + "] not supported"); - } - return ret; - } - - public byte[] pack(Object... args) { - if (args.length != this.formats.size()) { - throw new IllegalArgumentException("argument size " + args.length + - " doesn't match format size " + this.formats.size()); - } - ByteBuffer bf = ByteBuffer.allocate(this.calcSize()); - bf.order(this.byteOrder); - for (int i = 0; i < args.length; i++) { - Object arg = args[i]; - Class format = (Class) formats.get(i)[0]; - int size = (int) formats.get(i)[1]; - log.debug("Index[" + i + "], fmt = " + format + ", arg = " + arg + ", multi = " + size); - - //padding - if (format == PadByte.class) { - byte b[] = new byte[size]; - if (arg == null) { - Arrays.fill(b, (byte) 0); - } else if (arg instanceof Byte) { - Arrays.fill(b, (byte) arg); - } else if (arg instanceof Integer) { - Arrays.fill(b, ((Integer) arg).byteValue()); - } else { - throw new IllegalArgumentException("Index[" + i + "] Unsupported arg [" + arg + "] with type [" + format + "]"); - } - bf.put(b); - continue; - } - - //signed byte - if (arg instanceof byte[]) { - bf.put((byte[]) arg); - int paddingSize = size - ((byte[]) arg).length; - if (0 < paddingSize) { - byte padBytes[] = new byte[size - ((byte[]) arg).length]; - Arrays.fill(padBytes, (byte) 0); - bf.put(padBytes); - } else if (0 > paddingSize) { - log.error("container size " + size + ", value size " + ((byte[]) arg).length); - throw new IllegalArgumentException("Index[" + i + "] arg [" + arg + "] with type [" + format + "] size overflow"); - } else { - log.debug("perfect match, paddingSize is zero"); - } - continue; - } - - //unsigned byte - if (arg instanceof int[] && format == Byte.class) { - for (int v : (int[]) arg) { - if (v > 255 || v < 0) { - throw new IllegalArgumentException("Index[" + i + "] Unsupported [int array] arg [" + arg + "] with type [" + format + "]"); - } - bf.put((byte) v); - } - continue; - } - - if (arg instanceof Short) { - bf.putShort((short) arg); - continue; - } - - if (arg instanceof Integer) { - if (format == Integer.class) { - bf.putInt((int) arg); - } else if (format == UnsignedShort.class) { - ByteBuffer bf2 = ByteBuffer.allocate(4); - bf2.order(this.byteOrder); - bf2.putInt((int) arg); - bf2.flip(); - if (this.byteOrder == ByteOrder.LITTLE_ENDIAN) {//LE - bf.putShort(bf2.getShort()); - bf2.getShort();//discard - } else {//BE - bf2.getShort();//discard - bf.putShort(bf2.getShort()); - } - } else if (format == UnsignedInt.class) { - if ((Integer) arg < 0) { - throw new IllegalArgumentException("Index[" + i + "] Unsupported [Integer] arg [" + arg + "] with type [" + format + "]"); - } - bf.putInt((int) arg); - } else { - throw new IllegalArgumentException("Index[" + i + "] Unsupported [Integer] arg [" + arg + "] with type [" + format + "]"); - } - continue; - } - - if (arg instanceof Long) { - //XXX: maybe run into issue if we meet REAL Unsigned Long - if (format == Long.class || format == UnsignedLong.class) { - bf.putLong((long) arg); - } else if (format == UnsignedInt.class) { - if ((Long) arg < 0L || (Long) arg > (Integer.MAX_VALUE * 2L + 1)) { - throw new IllegalArgumentException("Index[" + i + "] Unsupported [Long] arg [" + arg + "] with type [" + format + "]"); - } - ByteBuffer bf2 = ByteBuffer.allocate(8); - bf2.order(this.byteOrder); - bf2.putLong((long) arg); - bf2.flip(); - if (this.byteOrder == ByteOrder.LITTLE_ENDIAN) {//LE - bf.putInt(bf2.getInt()); - bf2.getInt();//discard - } else {//BE - bf2.getInt();//discard - bf.putInt(bf2.getInt()); - } - } else { - throw new IllegalArgumentException("Index[" + i + "] Unsupported arg [" + arg + "] with type [" + format + "]"); - } - } - } - log.debug("Pack Result:" + Helper.Companion.toHexString(bf.array())); - return bf.array(); - } - - private static class UnsignedInt { - } - - private static class UnsignedLong { - } - - private static class UnsignedShort { - } - - private static class PadByte { - } -} diff --git a/bbootimg/src/main/kotlin/Helper.kt b/bbootimg/src/main/kotlin/Helper.kt index d445adc..7aac2cc 100644 --- a/bbootimg/src/main/kotlin/Helper.kt +++ b/bbootimg/src/main/kotlin/Helper.kt @@ -35,6 +35,19 @@ class Helper { return baos.toByteArray() } + fun ByteArray.paddingWith(pageSize: UInt, paddingHead: Boolean = false): ByteArray { + val paddingNeeded = round_to_multiple(this.size.toUInt(), pageSize) - this.size.toUInt() + return if (paddingNeeded > 0u) { + if (paddingHead) { + join(Struct3("${paddingNeeded}x").pack(null), this) + } else { + join(this, Struct3("${paddingNeeded}x").pack(null)) + } + } else { + this + } + } + fun join(vararg source: ByteArray): ByteArray { val baos = ByteArrayOutputStream() for (src in source) { @@ -275,7 +288,7 @@ class Helper { "sha256" -> "sha-256" "sha384" -> "sha-384" "sha512" -> "sha-512" - else -> throw IllegalArgumentException("unknown algorithm: $alg") + else -> throw IllegalArgumentException("unknown algorithm: [$alg]") } } diff --git a/bbootimg/src/main/kotlin/ParamConfig.kt b/bbootimg/src/main/kotlin/ParamConfig.kt index 7a23d2e..73c88c0 100644 --- a/bbootimg/src/main/kotlin/ParamConfig.kt +++ b/bbootimg/src/main/kotlin/ParamConfig.kt @@ -9,4 +9,4 @@ data class ParamConfig( 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") + val mkbootimg: String = "./tools/mkbootimg") diff --git a/bbootimg/src/main/kotlin/R.kt b/bbootimg/src/main/kotlin/R.kt deleted file mode 100755 index 6c36dd7..0000000 --- a/bbootimg/src/main/kotlin/R.kt +++ /dev/null @@ -1,104 +0,0 @@ -package cfig - -import cfig.bootimg.BootImgInfo -import de.vandermeer.asciitable.AsciiTable -import org.slf4j.LoggerFactory -import java.io.File -import kotlin.system.exitProcess - -@ExperimentalUnsignedTypes -fun main(args: Array) { - val log = LoggerFactory.getLogger("Launcher") - if ((args.size == 6) && args[0] in setOf("pack", "unpack", "sign")) { - if (args[1] == "vbmeta.img") { - when (args[0]) { - "unpack" -> { - if (File(UnifiedConfig.workDir).exists()) File(UnifiedConfig.workDir).deleteRecursively() - File(UnifiedConfig.workDir).mkdirs() - Avb().parseVbMeta(args[1]) - } - "pack" -> { - Avb().packVbMetaWithPadding(null) - } - "sign" -> { - log.info("vbmeta is already signed") - } - } - } else { - when (args[0]) { - "unpack" -> { - if (File(UnifiedConfig.workDir).exists()) File(UnifiedConfig.workDir).deleteRecursively() - File(UnifiedConfig.workDir).mkdirs() - val info = Parser().parseBootImgHeader(fileName = args[1], avbtool = args[3]) - InfoTable.instance.addRule() - InfoTable.instance.addRow("image info", ParamConfig().cfg) - if (info.signatureType == BootImgInfo.VerifyType.AVB) { - log.info("continue to analyze vbmeta info in " + args[1]) - Avb().parseVbMeta(args[1]) - InfoTable.instance.addRule() - InfoTable.instance.addRow("AVB info", Avb.getJsonFileName(args[1])) - if (File("vbmeta.img").exists()) { - Avb().parseVbMeta("vbmeta.img") - } - } - Parser().extractBootImg(fileName = args[1], info2 = info) - - InfoTable.instance.addRule() - val tableHeader = AsciiTable().apply { - addRule() - addRow("What", "Where") - addRule() - } - log.info("\n\t\t\tUnpack Summary of ${args[1]}\n{}\n{}", tableHeader.render(), InfoTable.instance.render()) - log.info("Following components are not present: ${InfoTable.missingParts}") - } - "pack" -> { - Packer().pack(mkbootfsBin = args[5]) - } - "sign" -> { - Signer.sign(avbtool = args[3], bootSigner = args[4]) - 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.img info updated") -// Avb().packVbMetaWithPadding() - } else { - log.info("no vbmeta.img need to update") - } - }//end-of-avb - }//end-of-sign - } - } - } else { - println("Usage: unpack ") - println("Usage: pack ") - println("Usage: sign ") - exitProcess(1) - } -} - -/* - (a * x) mod m == 1 - */ -// fun modInv(a: Int, m: Int): Int { -// for (x in 0 until m) { -// if (a * x % m == 1) { -// return x -// } -// } -// throw IllegalArgumentException("modular multiplicative inverse of [$a] under modulo [$m] doesn't exist") -// } -// diff --git a/bbootimg/src/main/kotlin/Signer.kt b/bbootimg/src/main/kotlin/Signer.kt index 7aff834..6811dac 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.Avb.Companion.getJsonFileName import cfig.bootimg.BootImgInfo import com.fasterxml.jackson.databind.ObjectMapper import org.apache.commons.exec.CommandLine @@ -38,16 +39,10 @@ class Signer { //our signer File(cfg.info.output + ".clear").copyTo(File(cfg.info.output + ".signed")) - Avb().add_hash_footer(cfg.info.output + ".signed", + Avb().addHashFooter(cfg.info.output + ".signed", info2.imageSize, - use_persistent_digest = false, - do_not_use_ab = false, - salt = Helper.toHexString(bootDesc.salt), - hash_algorithm = bootDesc.hash_algorithm_str, partition_name = bootDesc.partition_name, - rollback_index = ai.header!!.rollback_index.toLong(), - common_algorithm = alg!!.name, - inReleaseString = ai.header!!.release_string) + newAvbInfo = ObjectMapper().readValue(File(getJsonFileName(cfg.info.output)), AVBInfo::class.java)) //original signer File(cfg.info.output + ".clear").copyTo(File(cfg.info.output + ".signed2")) var cmdlineStr = "$avbtool add_hash_footer " + @@ -55,8 +50,8 @@ class Signer { "--partition_size ${info2.imageSize} " + "--salt ${Helper.toHexString(bootDesc.salt)} " + "--partition_name ${bootDesc.partition_name} " + - "--hash_algorithm ${bootDesc.hash_algorithm_str} " + - "--algorithm ${alg.name} " + "--hash_algorithm ${bootDesc.hash_algorithm} " + + "--algorithm ${alg!!.name} " if (alg.defaultKey.isNotBlank()) { cmdlineStr += "--key ${alg.defaultKey}" } diff --git a/bbootimg/src/main/kotlin/avb/AVBInfo.kt b/bbootimg/src/main/kotlin/avb/AVBInfo.kt index 4dbee39..3c7ca76 100755 --- a/bbootimg/src/main/kotlin/avb/AVBInfo.kt +++ b/bbootimg/src/main/kotlin/avb/AVBInfo.kt @@ -1,5 +1,10 @@ package avb +import avb.blob.AuthBlob +import avb.blob.AuxBlob +import avb.blob.Footer +import avb.blob.Header + /* a wonderfaul base64 encoder/decoder: https://cryptii.com/base64-to-hex */ diff --git a/bbootimg/src/main/kotlin/avb/AuthBlob.kt b/bbootimg/src/main/kotlin/avb/AuthBlob.kt deleted file mode 100644 index 6d21421..0000000 --- a/bbootimg/src/main/kotlin/avb/AuthBlob.kt +++ /dev/null @@ -1,8 +0,0 @@ -package avb - -@ExperimentalUnsignedTypes -data class AuthBlob( - var offset: ULong = 0U, - var size: ULong = 0U, - var hash: String? = null, - var signature: String? = null) \ No newline at end of file diff --git a/bbootimg/src/main/kotlin/avb/Avb.kt b/bbootimg/src/main/kotlin/avb/Avb.kt index 7f9bcef..83ce953 100755 --- a/bbootimg/src/main/kotlin/avb/Avb.kt +++ b/bbootimg/src/main/kotlin/avb/Avb.kt @@ -1,9 +1,13 @@ package cfig -import avb.* +import avb.AVBInfo import avb.alg.Algorithms +import avb.blob.AuthBlob +import avb.blob.AuxBlob +import avb.blob.Footer +import avb.blob.Header import avb.desc.* -import avb.AuxBlob +import cfig.Helper.Companion.paddingWith import cfig.io.Struct3 import com.fasterxml.jackson.databind.ObjectMapper import org.apache.commons.codec.binary.Hex @@ -14,7 +18,6 @@ import java.io.FileOutputStream import java.nio.file.Files import java.nio.file.Paths import java.nio.file.StandardOpenOption -import java.security.MessageDigest @ExperimentalUnsignedTypes class Avb { @@ -22,227 +25,119 @@ class Avb { private val MAX_FOOTER_SIZE = 4096 private val BLOCK_SIZE = 4096 - private var required_libavb_version_minor = 0 - - //migrated from: avbtool::Avb::add_hash_footer - fun add_hash_footer(image_file: String, - partition_size: Long, //aligned by Avb::BLOCK_SIZE - use_persistent_digest: Boolean, - do_not_use_ab: Boolean, - salt: String, - hash_algorithm: String, - partition_name: String, - rollback_index: Long, - common_algorithm: String, - inReleaseString: String?) { - log.info("add_hash_footer($image_file) ...") - var original_image_size: ULong - //required libavb version - if (use_persistent_digest || do_not_use_ab) { - required_libavb_version_minor = 1 - } - log.info("Required_libavb_version: 1.$required_libavb_version_minor") - - // SIZE + metadata (footer + vbmeta struct) - val max_metadata_size = MAX_VBMETA_SIZE + MAX_FOOTER_SIZE - if (partition_size < max_metadata_size) { - throw IllegalArgumentException("Parition SIZE of $partition_size is too small. " + - "Needs to be at least $max_metadata_size") - } - val max_image_size = partition_size - max_metadata_size - log.info("max_image_size: $max_image_size") - - //TODO: typical block size = 4096L, from avbtool::Avb::ImageHandler::block_size - //since boot.img is not in sparse format, we are safe to hardcode it to 4096L for now - if (partition_size % BLOCK_SIZE != 0L) { - throw IllegalArgumentException("Partition SIZE of $partition_size is not " + - "a multiple of the image block SIZE 4096") - } - - //truncate AVB footer if there is. Then add_hash_footer() is idempotent - val fis = FileInputStream(image_file) - val originalFileSize = File(image_file).length() - if (originalFileSize > 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("original image $image_file has AVB footer, " + - "truncate it to original SIZE: ${footer.originalImageSize}") - it.truncate(footer.originalImageSize.toLong()) + //migrated from: avbtool::Avb::addHashFooter + fun addHashFooter(image_file: String, + partition_size: Long, //aligned by Avb::BLOCK_SIZE + partition_name: String, + newAvbInfo: AVBInfo) { + log.info("addHashFooter($image_file) ...") + + imageSizeCheck(partition_size, image_file) + + //truncate AVB footer if there is. Then addHashFooter() is idempotent + trimFooter(image_file) + val newImageSize = File(image_file).length() + + //VBmeta blob: update hash descriptor + newAvbInfo.apply { + val itr = this.auxBlob!!.hashDescriptors.iterator() + var hd = HashDescriptor() + while (itr.hasNext()) {//remove previous hd entry + val itrValue = itr.next() + if (itrValue.partition_name == partition_name) { + itr.remove() + hd = itrValue + } } - } catch (e: IllegalArgumentException) { - log.info("original image $image_file doesn't have AVB footer") - original_image_size = originalFileSize.toULong() + //HashDescriptor + hd.update(image_file) + log.info("updated hash descriptor:" + Hex.encodeHexString(hd.encode())) + this.auxBlob!!.hashDescriptors.add(hd) } - //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") - } + val vbmetaBlob = packVbMeta(newAvbInfo) + log.debug("vbmeta_blob: " + Helper.toHexString(vbmetaBlob)) + Helper.dumpToFile("hashDescriptor.vbmeta.blob", vbmetaBlob) - //hash digest - val digest = MessageDigest.getInstance(Helper.pyAlg2java(hash_algorithm)).apply { - update(saltByteArray) - update(File(image_file).readBytes()) - }.digest() - log.info("Digest(salt + file): " + Helper.toHexString(digest)) - - //HashDescriptor - val hd = HashDescriptor() - hd.image_size = File(image_file).length().toULong() - hd.hash_algorithm = hash_algorithm - hd.partition_name = partition_name - hd.salt = saltByteArray - hd.flags = 0U - if (do_not_use_ab) hd.flags = hd.flags or 1u - 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), - null, - rollback_index, - 0, - null, - null, - 0U, - 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.toLong() % BLOCK_SIZE != 0L) { - val padding_needed = BLOCK_SIZE - (hd.image_size.toLong() % BLOCK_SIZE) - FileOutputStream(image_file, true).use { fos -> - fos.write(ByteArray(padding_needed.toInt())) - } - log.info("$image_file padded: ${hd.image_size} -> ${File(image_file).length()}") - } else { - log.info("$image_file doesn't need padding") - } + val imgPaddingNeeded = Helper.round_to_multiple(newImageSize, BLOCK_SIZE) - newImageSize // + 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, Struct3("${padding_needed}x").pack(null)) - FileOutputStream(image_file, true).use { fos -> - fos.write(vbmeta_blob_with_padding) - } + val vbmetaOffset = File(image_file).length() + val vbmetaBlobWithPadding = vbmetaBlob.paddingWith(BLOCK_SIZE.toUInt()) // + 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(Struct3("${partition_size - vbmeta_end_offset - 1 * BLOCK_SIZE}x").pack(null)) - } + val vbmetaEndOffset = vbmetaOffset + vbmetaBlobWithPadding.size + val dontCareChunkSize = partition_size - vbmetaEndOffset - 1 * BLOCK_SIZE // + AvbFooter + padding - log.info("Appending footer ...") - val footer = Footer() - footer.originalImageSize = original_image_size - footer.vbMetaOffset = vbmeta_offset.toULong() - footer.vbMetaSize = vbmeta_blob.size.toULong() - val footerBob = footer.encode() - val footerBlobWithPadding = Helper.join( - Struct3("${BLOCK_SIZE - Footer.SIZE}x").pack(null), footerBob) - log.info("footer:" + Helper.toHexString(footerBob)) - log.info(footer.toString()) + newAvbInfo.footer!!.apply { + originalImageSize = newImageSize.toULong() + vbMetaOffset = vbmetaOffset.toULong() + vbMetaSize = vbmetaBlob.size.toULong() + } + log.info(newAvbInfo.footer.toString()) + val footerBlobWithPadding = newAvbInfo.footer!!.encode().paddingWith(BLOCK_SIZE.toUInt(), true) + FileOutputStream(image_file, true).use { fos -> + log.info("1/4 Padding image with $imgPaddingNeeded bytes ...") + fos.write(ByteArray(imgPaddingNeeded.toInt())) + + log.info("2/4 Appending vbmeta (${vbmetaBlobWithPadding.size} bytes)...") + fos.write(vbmetaBlobWithPadding) + + log.info("3/4 Appending DONT CARE CHUNK ($dontCareChunkSize bytes) ...") + fos.write(ByteArray(dontCareChunkSize.toInt())) + + log.info("4/4 Appending AVB footer (${footerBlobWithPadding.size} bytes)...") fos.write(footerBlobWithPadding) } - log.info("add_hash_footer($image_file) done ...") + log.info("addHashFooter($image_file) done.") } - //avbtool::Avb::_generate_vbmeta_blob() - private fun generateVbMetaBlob(algorithm_name: String, - public_key_metadata_path: String?, - descriptors: Array, - chain_partitions: String?, - inRollbackIndex: Long, - inFlags: Long, - props: Map?, - kernel_cmdlines: List?, - required_libavb_version_minor: UInt, - 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()) + private fun trimFooter(image_file: String) { + var footer: Footer? = null + FileInputStream(image_file).use { + it.skip(File(image_file).length() - 64) + try { + footer = Footer(it) + log.info("original image $image_file has AVB footer") + } catch (e: IllegalArgumentException) { + log.info("original image $image_file doesn't have AVB footer") } } - kernel_cmdlines?.let { - it.forEach { eachCmdline -> - Helper.join(encodedDesc, KernelCmdlineDescriptor(cmdline = eachCmdline).encode()) + footer?.let { + FileOutputStream(File(image_file), true).channel.use { fc -> + log.info("original image $image_file has AVB footer, " + + "truncate it to original SIZE: ${it.originalImageSize}") + fc.truncate(it.originalImageSize.toLong()) } } - //algorithm - val alg = Algorithms.get(algorithm_name)!! - //encoded pubkey - val encodedKey = AuxBlob.encodePubKey(alg) - - //3 - whole aux blob - val auxBlob = Blob.getAuxDataBlob(encodedDesc, encodedKey, byteArrayOf()) - - //1 - whole header blob - val headerBlob = Header().apply { - bump_required_libavb_version_minor(required_libavb_version_minor) - auxiliary_data_block_size = auxBlob.size.toULong() - - authentication_data_block_size = Helper.round_to_multiple( - (alg.hash_num_bytes + alg.signature_num_bytes).toLong(), 64).toULong() - - algorithm_type = alg.algorithm_type.toUInt() - - hash_offset = 0U - hash_size = alg.hash_num_bytes.toULong() - - signature_offset = alg.hash_num_bytes.toULong() - signature_size = alg.signature_num_bytes.toULong() - - descriptors_offset = 0U - descriptors_size = encodedDesc.size.toULong() - - public_key_offset = descriptors_size - public_key_size = encodedKey.size.toULong() - - //TODO: support pubkey metadata - public_key_metadata_size = 0U - public_key_metadata_offset = public_key_offset + public_key_size + } - rollback_index = inRollbackIndex.toULong() - flags = inFlags.toUInt() - if (inReleaseString != null) { - log.info("Using preset release string: $inReleaseString") - this.release_string = inReleaseString - } - }.encode() + private fun imageSizeCheck(partition_size: Long, image_file: String) { + //image size sanity check + val maxMetadataSize = MAX_VBMETA_SIZE + MAX_FOOTER_SIZE + if (partition_size < maxMetadataSize) { + throw IllegalArgumentException("Parition SIZE of $partition_size is too small. " + + "Needs to be at least $maxMetadataSize") + } + val maxImageSize = partition_size - maxMetadataSize + log.info("max_image_size: $maxImageSize") - //2 - auth blob - val authBlob = Blob.getAuthBlob(headerBlob, auxBlob, algorithm_name) + //TODO: typical block size = 4096L, from avbtool::Avb::ImageHandler::block_size + //since boot.img is not in sparse format, we are safe to hardcode it to 4096L for now + if (partition_size % BLOCK_SIZE != 0L) { + throw IllegalArgumentException("Partition SIZE of $partition_size is not " + + "a multiple of the image block SIZE 4096") + } - return Helper.join(headerBlob, authBlob, auxBlob) + val originalFileSize = File(image_file).length() + if (originalFileSize > maxImageSize) { + throw IllegalArgumentException("Image size of $originalFileSize exceeds maximum image size " + + "of $maxImageSize in order to fit in a partition size of $partition_size.") + } } fun parseVbMeta(image_file: String): AVBInfo { @@ -250,6 +145,7 @@ class Avb { val jsonFile = getJsonFileName(image_file) var footer: Footer? = null var vbMetaOffset: ULong = 0U + // footer FileInputStream(image_file).use { fis -> fis.skip(File(image_file).length() - Footer.SIZE) try { @@ -261,6 +157,7 @@ class Avb { } } + // header var vbMetaHeader = Header() FileInputStream(image_file).use { fis -> fis.skip(vbMetaOffset.toLong()) @@ -273,34 +170,69 @@ class Avb { val auxBlockOffset = authBlockOffset + vbMetaHeader.authentication_data_block_size val descStartOffset = auxBlockOffset + vbMetaHeader.descriptors_offset - val ai = AVBInfo() - ai.footer = footer - ai.auxBlob = AuxBlob() - ai.header = vbMetaHeader - if (vbMetaHeader.public_key_size > 0U) { - ai.auxBlob!!.pubkey = AuxBlob.PubKeyInfo() - ai.auxBlob!!.pubkey!!.offset = vbMetaHeader.public_key_offset.toLong() - ai.auxBlob!!.pubkey!!.size = vbMetaHeader.public_key_size.toLong() - } - if (vbMetaHeader.public_key_metadata_size > 0U) { - ai.auxBlob!!.pubkeyMeta = AuxBlob.PubKeyMetadataInfo() - ai.auxBlob!!.pubkeyMeta!!.offset = vbMetaHeader.public_key_metadata_offset.toLong() - ai.auxBlob!!.pubkeyMeta!!.size = vbMetaHeader.public_key_metadata_size.toLong() + val ai = AVBInfo(vbMetaHeader, null, AuxBlob(), footer) + + // Auth blob + if (vbMetaHeader.authentication_data_block_size > 0U) { + FileInputStream(image_file).use { fis -> + fis.skip(vbMetaOffset.toLong()) + fis.skip(Header.SIZE.toLong()) + fis.skip(vbMetaHeader.hash_offset.toLong()) + val ba = ByteArray(vbMetaHeader.hash_size.toInt()) + fis.read(ba) + log.debug("Parsed Auth Hash (Header & Aux Blob): " + Hex.encodeHexString(ba)) + val bb = ByteArray(vbMetaHeader.signature_size.toInt()) + fis.read(bb) + log.debug("Parsed Auth Signature (of hash): " + Hex.encodeHexString(bb)) + + ai.authBlob = AuthBlob() + ai.authBlob!!.offset = authBlockOffset + ai.authBlob!!.size = vbMetaHeader.authentication_data_block_size + ai.authBlob!!.hash = Hex.encodeHexString(ba) + ai.authBlob!!.signature = Hex.encodeHexString(bb) + } } + // aux - desc var descriptors: List = mutableListOf() if (vbMetaHeader.descriptors_size > 0U) { FileInputStream(image_file).use { fis -> fis.skip(descStartOffset.toLong()) descriptors = UnknownDescriptor.parseDescriptors2(fis, vbMetaHeader.descriptors_size.toLong()) } - descriptors.forEach { log.debug(it.toString()) + when (it) { + is PropertyDescriptor -> { + ai.auxBlob!!.propertyDescriptor.add(it) + } + is HashDescriptor -> { + ai.auxBlob!!.hashDescriptors.add(it) + } + is KernelCmdlineDescriptor -> { + ai.auxBlob!!.kernelCmdlineDescriptor.add(it) + } + is HashTreeDescriptor -> { + ai.auxBlob!!.hashTreeDescriptor.add(it) + } + is ChainPartitionDescriptor -> { + ai.auxBlob!!.chainPartitionDescriptor.add(it) + } + is UnknownDescriptor -> { + ai.auxBlob!!.unknownDescriptors.add(it) + } + else -> { + throw IllegalArgumentException("invalid descriptor: $it") + } + } } } - + // aux - pubkey if (vbMetaHeader.public_key_size > 0U) { + ai.auxBlob!!.pubkey = AuxBlob.PubKeyInfo() + ai.auxBlob!!.pubkey!!.offset = vbMetaHeader.public_key_offset.toLong() + ai.auxBlob!!.pubkey!!.size = vbMetaHeader.public_key_size.toLong() + FileInputStream(image_file).use { fis -> fis.skip(auxBlockOffset.toLong()) fis.skip(vbMetaHeader.public_key_offset.toLong()) @@ -309,65 +241,21 @@ class Avb { log.debug("Parsed Pub Key: " + Hex.encodeHexString(ai.auxBlob!!.pubkey!!.pubkey)) } } - + // aux - pkmd if (vbMetaHeader.public_key_metadata_size > 0U) { - FileInputStream(image_file).use { fis -> - fis.skip(vbMetaOffset.toLong()) - fis.skip(Header.SIZE.toLong()) - fis.skip(vbMetaHeader.public_key_metadata_offset.toLong()) - val ba = ByteArray(vbMetaHeader.public_key_metadata_size.toInt()) - fis.read(ba) - log.debug("Parsed Pub Key Metadata: " + Hex.encodeHexString(ba)) - } - } + ai.auxBlob!!.pubkeyMeta = AuxBlob.PubKeyMetadataInfo() + ai.auxBlob!!.pubkeyMeta!!.offset = vbMetaHeader.public_key_metadata_offset.toLong() + ai.auxBlob!!.pubkeyMeta!!.size = vbMetaHeader.public_key_metadata_size.toLong() - if (vbMetaHeader.authentication_data_block_size > 0U) { FileInputStream(image_file).use { fis -> - fis.skip(vbMetaOffset.toLong()) - fis.skip(Header.SIZE.toLong()) - fis.skip(vbMetaHeader.hash_offset.toLong()) - val ba = ByteArray(vbMetaHeader.hash_size.toInt()) - fis.read(ba) - log.debug("Parsed Auth Hash (Header & Aux Blob): " + Hex.encodeHexString(ba)) - val bb = ByteArray(vbMetaHeader.signature_size.toInt()) - fis.read(bb) - log.debug("Parsed Auth Signature (of hash): " + Hex.encodeHexString(bb)) - - ai.authBlob = AuthBlob() - ai.authBlob!!.offset = authBlockOffset - ai.authBlob!!.size = vbMetaHeader.authentication_data_block_size - ai.authBlob!!.hash = Hex.encodeHexString(ba) - ai.authBlob!!.signature = Hex.encodeHexString(bb) + fis.skip(auxBlockOffset.toLong()) + fis.skip(vbMetaHeader.public_key_metadata_offset.toLong()) + ai.auxBlob!!.pubkeyMeta!!.pkmd = ByteArray(vbMetaHeader.public_key_metadata_size.toInt()) + fis.read(ai.auxBlob!!.pubkeyMeta!!.pkmd) + log.debug("Parsed Pub Key Metadata: " + Helper.toHexString(ai.auxBlob!!.pubkeyMeta!!.pkmd)) } } - descriptors.forEach { - when (it) { - is PropertyDescriptor -> { - ai.auxBlob!!.propertyDescriptor.add(it) - } - is HashDescriptor -> { - ai.auxBlob!!.hashDescriptors.add(it) - } - is KernelCmdlineDescriptor -> { - ai.auxBlob!!.kernelCmdlineDescriptor.add(it) - } - is HashTreeDescriptor -> { - ai.auxBlob!!.hashTreeDescriptor.add(it) - } - is ChainPartitionDescriptor -> { - ai.auxBlob!!.chainPartitionDescriptor.add(it) - } - is UnknownDescriptor -> { - ai.auxBlob!!.unknownDescriptors.add(it) - } - else -> { - throw IllegalArgumentException("invalid descriptor: $it") - } - } - } - val aiStr = ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(ai) - log.debug(aiStr) ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(jsonFile), ai) log.info("vbmeta info written to $jsonFile") @@ -377,22 +265,9 @@ class Avb { private fun packVbMeta(info: AVBInfo? = null, image_file: String? = null): ByteArray { val ai = info ?: ObjectMapper().readValue(File(getJsonFileName(image_file!!)), AVBInfo::class.java) val alg = Algorithms.get(ai.header!!.algorithm_type.toInt())!! - val encodedDesc = ai.auxBlob!!.encodeDescriptors() - //encoded pubkey - val encodedKey = AuxBlob.encodePubKey(alg) //3 - whole aux blob - var auxBlob = byteArrayOf() - if (ai.header!!.auxiliary_data_block_size > 0U) { - if (encodedKey.contentEquals(ai.auxBlob!!.pubkey!!.pubkey)) { - log.info("Using the same key as original vbmeta") - } else { - log.warn("Using different key from original vbmeta") - } - auxBlob = Blob.getAuxDataBlob(encodedDesc, encodedKey, byteArrayOf()) - } else { - log.info("No aux blob") - } + val auxBlob = ai.auxBlob?.encode(alg) ?: byteArrayOf() //1 - whole header blob val headerBlob = ai.header!!.apply { @@ -401,7 +276,7 @@ class Avb { (alg.hash_num_bytes + alg.signature_num_bytes).toLong(), 64).toULong() descriptors_offset = 0U - descriptors_size = encodedDesc.size.toULong() + descriptors_size = ai.auxBlob?.descriptorSize?.toULong() ?: 0U hash_offset = 0U hash_size = alg.hash_num_bytes.toULong() @@ -410,17 +285,17 @@ class Avb { signature_size = alg.signature_num_bytes.toULong() public_key_offset = descriptors_size - public_key_size = encodedKey.size.toULong() + public_key_size = AuxBlob.encodePubKey(alg).size.toULong() - //TODO: support pubkey metadata - public_key_metadata_size = 0U + public_key_metadata_size = ai.auxBlob!!.pubkeyMeta?.pkmd?.size?.toULong() ?: 0U public_key_metadata_offset = public_key_offset + public_key_size + log.info("pkmd size: $public_key_metadata_size, pkmd offset : $public_key_metadata_offset") }.encode() //2 - auth blob var authBlob = byteArrayOf() if (ai.authBlob != null) { - authBlob = Blob.getAuthBlob(headerBlob, auxBlob, alg.name) + authBlob = AuthBlob.createBlob(headerBlob, auxBlob, alg.name) } else { log.info("No auth blob") } diff --git a/bbootimg/src/main/kotlin/avb/VBMeta.kt b/bbootimg/src/main/kotlin/avb/VBMeta.kt deleted file mode 100644 index 7584339..0000000 --- a/bbootimg/src/main/kotlin/avb/VBMeta.kt +++ /dev/null @@ -1,7 +0,0 @@ -package avb - -@ExperimentalUnsignedTypes -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/Blob.kt b/bbootimg/src/main/kotlin/avb/blob/AuthBlob.kt similarity index 68% rename from bbootimg/src/main/kotlin/avb/Blob.kt rename to bbootimg/src/main/kotlin/avb/blob/AuthBlob.kt index 327b0de..1113c7f 100644 --- a/bbootimg/src/main/kotlin/avb/Blob.kt +++ b/bbootimg/src/main/kotlin/avb/blob/AuthBlob.kt @@ -1,4 +1,4 @@ -package avb +package avb.blob import avb.alg.Algorithms import cfig.Helper @@ -6,20 +6,14 @@ import cfig.io.Struct3 import org.slf4j.LoggerFactory import java.security.MessageDigest -class Blob { - @ExperimentalUnsignedTypes +@ExperimentalUnsignedTypes +data class AuthBlob( + var offset: ULong = 0U, + var size: ULong = 0U, + var hash: String? = null, + var signature: String? = null) { companion object { - private val log = LoggerFactory.getLogger(Blob::class.java) - - //encoded_descriptors + encoded_key + pkmd_blob + (padding) - fun getAuxDataBlob(encodedDesc: ByteArray, encodedKey: ByteArray, pkmdBlob: ByteArray): ByteArray { - val auxSize = Helper.round_to_multiple( - (encodedDesc.size + encodedKey.size + pkmdBlob.size).toLong(), - 64) - return Struct3("${auxSize}b").pack(Helper.join(encodedDesc, encodedKey, pkmdBlob)) - } - - fun getAuthBlob(header_data_blob: ByteArray, + fun createBlob(header_data_blob: ByteArray, aux_data_blob: ByteArray, algorithm_name: String): ByteArray { val alg = Algorithms.get(algorithm_name)!! @@ -43,5 +37,7 @@ class Blob { val authData = Helper.join(binaryHash, binarySignature) return Helper.join(authData, Struct3("${authBlockSize - authData.size}x").pack(0)) } + + private val log = LoggerFactory.getLogger(AuthBlob::class.java) } -} +} \ No newline at end of file diff --git a/bbootimg/src/main/kotlin/avb/AuxBlob.kt b/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt similarity index 66% rename from bbootimg/src/main/kotlin/avb/AuxBlob.kt rename to bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt index ec86e20..9d69e32 100644 --- a/bbootimg/src/main/kotlin/avb/AuxBlob.kt +++ b/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt @@ -1,16 +1,19 @@ -package avb +package avb.blob import avb.alg.Algorithm import avb.desc.* import cfig.Helper import cfig.io.Struct3 +import com.fasterxml.jackson.annotation.JsonIgnore +import com.fasterxml.jackson.annotation.JsonIgnoreProperties import org.junit.Assert import org.slf4j.LoggerFactory import java.nio.file.Files import java.nio.file.Paths @ExperimentalUnsignedTypes -data class AuxBlob( +@JsonIgnoreProperties("descriptorSize") +class AuxBlob( var pubkey: PubKeyInfo? = null, var pubkeyMeta: PubKeyMetadataInfo? = null, var propertyDescriptor: MutableList = mutableListOf(), @@ -18,8 +21,13 @@ data class AuxBlob( var hashDescriptors: MutableList = mutableListOf(), var kernelCmdlineDescriptor: MutableList = mutableListOf(), var chainPartitionDescriptor: MutableList = mutableListOf(), - var unknownDescriptors: MutableList = mutableListOf() -) { + var unknownDescriptors: MutableList = mutableListOf()) { + + val descriptorSize: Int + get(): Int { + return this.encodeDescriptors().size + } + data class PubKeyInfo( var offset: Long = 0L, var size: Long = 0L, @@ -32,8 +40,7 @@ data class AuxBlob( var pkmd: ByteArray = byteArrayOf() ) - fun encodeDescriptors(): ByteArray { - var ret = byteArrayOf() + private fun encodeDescriptors(): ByteArray { return mutableListOf().let { descList -> arrayOf(this.propertyDescriptor, //tag 0 this.hashTreeDescriptor, //tag 1 @@ -44,6 +51,7 @@ data class AuxBlob( ).forEach { typedList -> typedList.forEach { descList.add(it) } } + var ret = byteArrayOf() descList.sortBy { it.sequence } descList.forEach { ret = Helper.join(ret, it.encode()) } ret @@ -51,14 +59,33 @@ data class AuxBlob( } //encoded_descriptors + encoded_key + pkmd_blob + (padding) - fun encode(): ByteArray { + fun encode(alg: Algorithm): ByteArray { + //descriptors 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 Struct3("${auxSize}b").pack( - Helper.joinWithNulls(encodedDesc, this.pubkey?.pubkey, this.pubkeyMeta?.pkmd)) + //pubkey + val encodedKey = encodePubKey(alg) + if (this.pubkey != null) { + if (encodedKey.contentEquals(this.pubkey!!.pubkey)) { + log.info("Using the same key as original vbmeta") + } else { + log.warn("Using different key from original vbmeta") + } + } else { + log.info("no pubkey in auxBlob") + } + //pkmd + var encodedPkmd = byteArrayOf() + if (this.pubkeyMeta != null) { + encodedPkmd = this.pubkeyMeta!!.pkmd + log.warn("adding pkmd [size=${this.pubkeyMeta!!.pkmd.size}]...") + } else { + log.info("no pubkey metadata in auxBlob") + } + + val auxSize = Helper.round_to_multiple( + (encodedDesc.size + encodedKey.size + encodedPkmd.size).toLong(), + 64) + return Struct3("${auxSize}b").pack(Helper.join(encodedDesc, encodedKey, encodedPkmd)) } companion object { diff --git a/bbootimg/src/main/kotlin/avb/Footer.kt b/bbootimg/src/main/kotlin/avb/blob/Footer.kt similarity index 77% rename from bbootimg/src/main/kotlin/avb/Footer.kt rename to bbootimg/src/main/kotlin/avb/blob/Footer.kt index 12329b2..e5f8b87 100644 --- a/bbootimg/src/main/kotlin/avb/Footer.kt +++ b/bbootimg/src/main/kotlin/avb/blob/Footer.kt @@ -1,4 +1,4 @@ -package avb +package avb.blob import cfig.io.Struct3 import org.junit.Assert @@ -32,19 +32,6 @@ data class Footer constructor( var vbMetaOffset: ULong = 0U, var vbMetaSize: ULong = 0U ) { - companion object { - const val MAGIC = "AVBf" - const val SIZE = 64 - private const val RESERVED = 28 - const val FOOTER_VERSION_MAJOR = 1U - const val FOOTER_VERSION_MINOR = 0U - private const val FORMAT_STRING = "!4s2L3Q${RESERVED}x" - - init { - Assert.assertEquals(SIZE, Struct3(FORMAT_STRING).calcSize()) - } - } - @Throws(IllegalArgumentException::class) constructor(iS: InputStream) : this() { val info = Struct3(FORMAT_STRING).unpack(iS) @@ -59,10 +46,13 @@ data class Footer constructor( vbMetaSize = info[5] as ULong } + constructor(originalImageSize: ULong, vbMetaOffset: ULong, vbMetaSize: ULong) + : this(FOOTER_VERSION_MAJOR, FOOTER_VERSION_MINOR, originalImageSize, vbMetaOffset, vbMetaSize) + @Throws(IllegalArgumentException::class) constructor(image_file: String) : this() { FileInputStream(image_file).use { fis -> - fis.skip(File(image_file).length() - Footer.SIZE) + fis.skip(File(image_file).length() - SIZE) val footer = Footer(fis) this.versionMajor = footer.versionMajor this.versionMinor = footer.versionMinor @@ -73,12 +63,25 @@ data class Footer constructor( } fun encode(): ByteArray { - return Struct3(FORMAT_STRING).pack(MAGIC, - this.versionMajor, - this.versionMinor, - this.originalImageSize, - this.vbMetaOffset, - this.vbMetaSize, - null) + return Struct3(FORMAT_STRING).pack(MAGIC, //4s + this.versionMajor, //L + this.versionMinor, //L + this.originalImageSize, //Q + this.vbMetaOffset, //Q + this.vbMetaSize, //Q + null) //${RESERVED}x + } + + companion object { + private const val MAGIC = "AVBf" + const val SIZE = 64 + private const val RESERVED = 28 + private const val FOOTER_VERSION_MAJOR = 1U + private const val FOOTER_VERSION_MINOR = 0U + private const val FORMAT_STRING = "!4s2L3Q${RESERVED}x" + + init { + Assert.assertEquals(SIZE, Struct3(FORMAT_STRING).calcSize()) + } } } \ No newline at end of file diff --git a/bbootimg/src/main/kotlin/avb/Header.kt b/bbootimg/src/main/kotlin/avb/blob/Header.kt similarity index 68% rename from bbootimg/src/main/kotlin/avb/Header.kt rename to bbootimg/src/main/kotlin/avb/blob/Header.kt index 7e3ce7b..007d0ed 100644 --- a/bbootimg/src/main/kotlin/avb/Header.kt +++ b/bbootimg/src/main/kotlin/avb/blob/Header.kt @@ -1,4 +1,4 @@ -package avb +package avb.blob import cfig.Avb import cfig.io.Struct3 @@ -56,21 +56,21 @@ data class Header( fun encode(): ByteArray { return Struct3(FORMAT_STRING).pack( - magic, - this.required_libavb_version_major, this.required_libavb_version_minor, - this.authentication_data_block_size, this.auxiliary_data_block_size, - this.algorithm_type, - this.hash_offset, this.hash_size, - this.signature_offset, this.signature_size, - this.public_key_offset, this.public_key_size, - this.public_key_metadata_offset, this.public_key_metadata_size, - this.descriptors_offset, this.descriptors_size, - this.rollback_index, - this.flags, - null, //${REVERSED0}x - this.release_string, //47s - null, //x - null) //${REVERSED}x + magic, //4s + this.required_libavb_version_major, this.required_libavb_version_minor, //2L + this.authentication_data_block_size, this.auxiliary_data_block_size, //2Q + this.algorithm_type, //L + this.hash_offset, this.hash_size, //hash 2Q + this.signature_offset, this.signature_size, //sig 2Q + this.public_key_offset, this.public_key_size, //pubkey 2Q + this.public_key_metadata_offset, this.public_key_metadata_size, //pkmd 2Q + this.descriptors_offset, this.descriptors_size, //desc 2Q + this.rollback_index, //Q + this.flags, //L + null, //${REVERSED0}x + this.release_string, //47s + null, //x + null) //${REVERSED}x } fun bump_required_libavb_version_minor(minor: UInt) { @@ -78,11 +78,11 @@ data class Header( } companion object { - const val magic: String = "AVB0" + private const val magic: String = "AVB0" const val SIZE = 256 private const val REVERSED0 = 4 private const val REVERSED = 80 - const val FORMAT_STRING = ("!4s2L2QL11QL${REVERSED0}x47sx" + "${REVERSED}x") + private const val FORMAT_STRING = ("!4s2L2QL11QL${REVERSED0}x47sx" + "${REVERSED}x") init { Assert.assertEquals(SIZE, Struct3(FORMAT_STRING).calcSize()) diff --git a/bbootimg/src/main/kotlin/avb/desc/HashDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/HashDescriptor.kt index c5188ca..842af35 100755 --- a/bbootimg/src/main/kotlin/avb/desc/HashDescriptor.kt +++ b/bbootimg/src/main/kotlin/avb/desc/HashDescriptor.kt @@ -2,22 +2,36 @@ package avb.desc import cfig.Helper import cfig.io.Struct3 +import org.apache.commons.codec.binary.Hex import org.junit.Assert +import org.slf4j.LoggerFactory import java.io.File +import java.io.FileInputStream import java.io.InputStream import java.security.MessageDigest @ExperimentalUnsignedTypes -class HashDescriptor(var image_size: ULong = 0U, +class HashDescriptor(var flags: UInt = 0U, + var partition_name: String = "", var hash_algorithm: String = "", - var hash_algorithm_str: String = "", + var image_size: ULong = 0U, + var salt: ByteArray = byteArrayOf(), + var digest: ByteArray = byteArrayOf(), var partition_name_len: UInt = 0U, var salt_len: UInt = 0U, - var digest_len: UInt = 0U, - var flags: UInt = 0U, - var partition_name: String = "", - var salt: ByteArray = byteArrayOf(), - var digest: ByteArray = byteArrayOf()) : Descriptor(TAG, 0U, 0) { + var digest_len: UInt = 0U) + : Descriptor(TAG, 0U, 0) { + var flagsInterpretation: String = "" + get() { + var ret = "" + if (this.flags and AVB_HASH_DESCRIPTOR_FLAGS_DO_NOT_USE_AB == 1U) { + ret += "1:no-A/B system" + } else { + ret += "0:A/B system" + } + return ret + } + constructor(data: InputStream, seq: Int = 0) : this() { val info = Struct3(FORMAT_STRING).unpack(data) this.tag = info[0] as ULong @@ -39,7 +53,6 @@ class HashDescriptor(var image_size: ULong = 0U, this.partition_name = payload[0] as String this.salt = payload[1] as ByteArray this.digest = payload[2] as ByteArray - this.hash_algorithm_str = this.hash_algorithm } override fun encode(): ByteArray { @@ -67,14 +80,54 @@ class HashDescriptor(var image_size: ULong = 0U, val digest = hasher.digest() } + fun update(image_file: String, use_persistent_digest: Boolean = false): HashDescriptor { + //salt + if (this.salt.isEmpty()) { + //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)) + this.salt = randomSalt + } + } else { + log.info("preset salt[${this.salt.size}] is valid: ${Hex.encodeHexString(this.salt)}") + } + + //size + this.image_size = File(image_file).length().toULong() + + //flags + if (this.flags and 1U == 1U) { + log.info("flag: use_ab = 0") + } else { + log.info("flag: use_ab = 1") + } + + if (!use_persistent_digest) { + //hash digest + val newDigest = MessageDigest.getInstance(Helper.pyAlg2java(hash_algorithm)).apply { + update(salt) + update(File(image_file).readBytes()) + }.digest() + log.info("Digest(salt + file): " + Helper.toHexString(newDigest)) + this.digest = newDigest + } + + return this + } + companion object { const val TAG: ULong = 2U private const val RESERVED = 60 private const val SIZE = 72 + RESERVED private const val FORMAT_STRING = "!3Q32s4L${RESERVED}x" + private val log = LoggerFactory.getLogger(HashDescriptor::class.java) + private const val AVB_HASH_DESCRIPTOR_FLAGS_DO_NOT_USE_AB = 1U } override fun toString(): String { return "HashDescriptor(TAG=$TAG, image_size=$image_size, hash_algorithm=$hash_algorithm, flags=$flags, partition_name='$partition_name', salt=${Helper.toHexString(salt)}, digest=${Helper.toHexString(digest)})" } -} \ No newline at end of file +} diff --git a/bbootimg/src/main/kotlin/avb/desc/HashTreeDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/HashTreeDescriptor.kt index 0857a2b..0bffc7a 100755 --- a/bbootimg/src/main/kotlin/avb/desc/HashTreeDescriptor.kt +++ b/bbootimg/src/main/kotlin/avb/desc/HashTreeDescriptor.kt @@ -2,12 +2,12 @@ package avb.desc import cfig.Helper import cfig.io.Struct3 -import org.slf4j.LoggerFactory import java.io.InputStream import java.util.* @ExperimentalUnsignedTypes class HashTreeDescriptor( + var flags: UInt = 0U, var dm_verity_version: UInt = 0u, var image_size: ULong = 0UL, var tree_offset: ULong = 0UL, @@ -20,8 +20,18 @@ class HashTreeDescriptor( var hash_algorithm: String = "", var partition_name: String = "", var salt: ByteArray = byteArrayOf(), - var root_digest: ByteArray = byteArrayOf(), - var flags: UInt = 0U) : Descriptor(TAG, 0U, 0) { + var root_digest: ByteArray = byteArrayOf()) : Descriptor(TAG, 0U, 0) { + var flagsInterpretation: String = "" + get() { + var ret = "" + if (this.flags and AVB_HASHTREE_DESCRIPTOR_FLAGS_DO_NOT_USE_AB == 1U) { + ret += "1:no-A/B system" + } else { + ret += "0:A/B system" + } + return ret + } + constructor(data: InputStream, seq: Int = 0) : this() { this.sequence = seq val info = Struct3(FORMAT_STRING).unpack(data) @@ -87,5 +97,6 @@ class HashTreeDescriptor( private const val RESERVED = 60L private const val SIZE = 120 + RESERVED private const val FORMAT_STRING = "!2QL3Q3L2Q32s4L${RESERVED}x" + private const val AVB_HASHTREE_DESCRIPTOR_FLAGS_DO_NOT_USE_AB = 1U } -} \ No newline at end of file +} diff --git a/bbootimg/src/main/kotlin/avb/desc/KernelCmdlineDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/KernelCmdlineDescriptor.kt index 693f414..9b80a60 100755 --- a/bbootimg/src/main/kotlin/avb/desc/KernelCmdlineDescriptor.kt +++ b/bbootimg/src/main/kotlin/avb/desc/KernelCmdlineDescriptor.kt @@ -9,7 +9,19 @@ import java.io.InputStream class KernelCmdlineDescriptor( var flags: UInt = 0U, var cmdlineLength: UInt = 0U, - var cmdline: String = "") : Descriptor(TAG, 0U, 0) { + var cmdline: String = "") + : Descriptor(TAG, 0U, 0) { + var flagsInterpretation: String = "" + get() { + var ret = "" + if (this.flags and flagHashTreeEnabled == flagHashTreeEnabled) { + ret += "$flagHashTreeEnabled: hashTree Enabled" + } else if (this.flags and flagHashTreeDisabled == flagHashTreeDisabled) { + ret += "$flagHashTreeDisabled: hashTree Disabled" + } + return ret + } + @Throws(IllegalArgumentException::class) constructor(data: InputStream, seq: Int = 0) : this() { val info = Struct3(FORMAT_STRING).unpack(data) @@ -42,8 +54,10 @@ class KernelCmdlineDescriptor( const val TAG: ULong = 3U const val SIZE = 24 const val FORMAT_STRING = "!2Q2L" //# tag, num_bytes_following (descriptor header), flags, cmdline length (bytes) - const val flagHashTreeEnabled = 1 - const val flagHashTreeDisabled = 2 + //AVB_KERNEL_CMDLINE_FLAGS_USE_ONLY_IF_HASHTREE_NOT_DISABLED + const val flagHashTreeEnabled = 1U + //AVB_KERNEL_CMDLINE_FLAGS_USE_ONLY_IF_HASHTREE_DISABLED + const val flagHashTreeDisabled = 2U init { Assert.assertEquals(SIZE, Struct3(FORMAT_STRING).calcSize()) diff --git a/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt index 98eb144..1e91526 100755 --- a/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt +++ b/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt @@ -31,7 +31,7 @@ class UnknownDescriptor(var data: ByteArray = byteArrayOf()) : Descriptor(0U, 0U return "UnknownDescriptor(tag=$tag, SIZE=${data.size}, data=${Hex.encodeHexString(data)}" } - fun analyze(): Any { + fun analyze(): Descriptor { return when (this.tag.toUInt()) { 0U -> { PropertyDescriptor(ByteArrayInputStream(this.encode()), this.sequence) @@ -82,9 +82,9 @@ class UnknownDescriptor(var data: ByteArray = byteArrayOf()) : Descriptor(0U, 0U return ret } - fun parseDescriptors2(stream: InputStream, totalSize: Long): List { + fun parseDescriptors2(stream: InputStream, totalSize: Long): List { log.info("Parse descriptors stream, SIZE = $totalSize") - val ret: MutableList = mutableListOf() + val ret: MutableList = mutableListOf() var currentSize = 0L var seq = 0 while (true) { diff --git a/bbootimg/src/main/kotlin/bootimg/BootImgHeader.kt b/bbootimg/src/main/kotlin/bootimg/BootImgHeader.kt index 09ea51a..88454d3 100644 --- a/bbootimg/src/main/kotlin/bootimg/BootImgHeader.kt +++ b/bbootimg/src/main/kotlin/bootimg/BootImgHeader.kt @@ -195,12 +195,14 @@ open class BootImgHeader( //refresh second bootloader size if (0U == this.secondBootloaderLength) { param.second = null + this.secondBootloaderOffset = 0U } else { this.secondBootloaderLength = File(param.second!!).length().toUInt() } //refresh recovery dtbo size if (0U == this.recoveryDtboLength) { param.dtbo = null + this.recoveryDtboOffset = 0U } else { this.recoveryDtboLength = File(param.dtbo!!).length().toUInt() } diff --git a/bbootimg/src/main/kotlin/bootimg/BootImgInfo.kt b/bbootimg/src/main/kotlin/bootimg/BootImgInfo.kt index 0c53a94..60b8898 100644 --- a/bbootimg/src/main/kotlin/bootimg/BootImgInfo.kt +++ b/bbootimg/src/main/kotlin/bootimg/BootImgInfo.kt @@ -79,9 +79,9 @@ class BootImgInfo(iS: InputStream?) : BootImgHeader(iS) { if (this.secondBootloaderLength > 0U) { ret.addArgument(" --second ") ret.addArgument(param.second) + ret.addArgument(" --second_offset ") + ret.addArgument("0x" + Integer.toHexString(this.secondBootloaderOffset.toInt())) } - ret.addArgument(" --second_offset ") - ret.addArgument("0x" + Integer.toHexString(this.secondBootloaderOffset.toInt())) if (!board.isBlank()) { ret.addArgument(" --board ") ret.addArgument(board) @@ -118,7 +118,7 @@ class BootImgInfo(iS: InputStream?) : BootImgHeader(iS) { ret.addArgument(" --output ") //ret.addArgument("boot.img" + ".google") - log.info("To Commandline: " + ret.toString()) + log.debug("To Commandline: " + ret.toString()) return ret } diff --git a/bbootimg/src/main/kotlin/bootimg/Packer.kt b/bbootimg/src/main/kotlin/bootimg/Packer.kt index d124942..7705d00 100644 --- a/bbootimg/src/main/kotlin/bootimg/Packer.kt +++ b/bbootimg/src/main/kotlin/bootimg/Packer.kt @@ -157,6 +157,7 @@ class Packer { val googleCmd = info2.toCommandLine().apply { addArgument(cfg.info.output + ".google") } + log.warn(googleCmd.toString()) DefaultExecutor().execute(googleCmd) val ourHash = hashFileAndSize(cfg.info.output + ".clear") diff --git a/bbootimg/src/main/kotlin/bootloader_message/BootloaderMsg.kt b/bbootimg/src/main/kotlin/bootloader_message/BootloaderMsg.kt index a1bde11..5fb4782 100644 --- a/bbootimg/src/main/kotlin/bootloader_message/BootloaderMsg.kt +++ b/bbootimg/src/main/kotlin/bootloader_message/BootloaderMsg.kt @@ -99,4 +99,21 @@ data class BootloaderMsg( } } } + + fun updateBootloaderMessage(command: String, recovery: String, options: Array?) { + this.command = command + this.recovery = "$recovery\n" + options?.forEach { + this.recovery += if (it.endsWith("\n")) { + it + } else { + it + "\n" + } + } + } + + fun updateBootFastboot() { + this.command = "boot-fastboot" + this.recovery = "" + } } diff --git a/bbootimg/src/main/kotlin/kernel_util/KernelExtractor.kt b/bbootimg/src/main/kotlin/kernel_util/KernelExtractor.kt index 57babcc..cad3254 100644 --- a/bbootimg/src/main/kotlin/kernel_util/KernelExtractor.kt +++ b/bbootimg/src/main/kotlin/kernel_util/KernelExtractor.kt @@ -34,7 +34,7 @@ class KernelExtractor { it.execute(cmd) log.info(cmd.toString()) val kernelVersion = File(kernelVersionFile).readLines() - log.info("kernel version: " + kernelVersion) + log.info("kernel version: $kernelVersion") log.info("kernel config dumped to : $kernelConfigFile") InfoTable.instance.addRow("\\-- version $kernelVersion", kernelVersionFile) InfoTable.instance.addRow("\\-- config", kernelConfigFile) diff --git a/bbootimg/src/main/kotlin/packable/BootImgParser.kt b/bbootimg/src/main/kotlin/packable/BootImgParser.kt index 48ebcbb..950f72b 100644 --- a/bbootimg/src/main/kotlin/packable/BootImgParser.kt +++ b/bbootimg/src/main/kotlin/packable/BootImgParser.kt @@ -12,7 +12,7 @@ class BootImgParser : IPackable { private val log = LoggerFactory.getLogger(BootImgParser::class.java) override fun capabilities(): List { - return listOf("^boot\\.img$", "^recovery\\.img$") + return listOf("^boot\\.img$", "^recovery\\.img$", "^recovery-two-step\\.img$") } override fun unpack(fileName: String) { diff --git a/bbootimg/src/test/kotlin/avb/BlobTest.kt b/bbootimg/src/test/kotlin/avb/BlobTest.kt index 33ebe14..395c5f4 100644 --- a/bbootimg/src/test/kotlin/avb/BlobTest.kt +++ b/bbootimg/src/test/kotlin/avb/BlobTest.kt @@ -1,6 +1,7 @@ package avb import avb.alg.Algorithms +import avb.blob.AuxBlob import org.apache.commons.codec.binary.Hex import org.junit.Assert.assertEquals import org.junit.Test diff --git a/bbootimg/src/test/kotlin/avb/FooterTest.kt b/bbootimg/src/test/kotlin/avb/FooterTest.kt index 94822a2..61cccb8 100644 --- a/bbootimg/src/test/kotlin/avb/FooterTest.kt +++ b/bbootimg/src/test/kotlin/avb/FooterTest.kt @@ -1,5 +1,6 @@ package avb +import avb.blob.Footer import org.apache.commons.codec.binary.Hex import org.junit.Test diff --git a/bbootimg/src/test/kotlin/avb/HeaderTest.kt b/bbootimg/src/test/kotlin/avb/HeaderTest.kt index 6d520a9..9dfcf55 100644 --- a/bbootimg/src/test/kotlin/avb/HeaderTest.kt +++ b/bbootimg/src/test/kotlin/avb/HeaderTest.kt @@ -1,5 +1,6 @@ package avb +import avb.blob.Header import org.apache.commons.codec.binary.Hex import org.junit.Test import java.io.ByteArrayInputStream diff --git a/bbootimg/src/test/kotlin/cfig/io/StructTest.kt b/bbootimg/src/test/kotlin/cfig/io/StructTest.kt deleted file mode 100644 index 7bbe8e0..0000000 --- a/bbootimg/src/test/kotlin/cfig/io/StructTest.kt +++ /dev/null @@ -1,135 +0,0 @@ -import cfig.Helper -import cfig.io.Struct -import com.fasterxml.jackson.databind.ObjectMapper -import org.junit.Test - -import org.junit.Assert.* -import java.io.ByteArrayInputStream -import kotlin.reflect.jvm.jvmName - -@ExperimentalUnsignedTypes -class StructTest { - private fun getConvertedFormats(inStruct: Struct): ArrayList> { - val f = inStruct.javaClass.getDeclaredField("formats") - f.isAccessible = true - val formatDumps = arrayListOf>() - (f.get(inStruct) as ArrayList<*>).apply { - this.forEach { - @Suppress("UNCHECKED_CAST") - val format = it as Array - formatDumps.add(mapOf(format[0].toString().split(" ")[1] to (format[1] as Int))) - } - } - return formatDumps - } - - private fun constructorTestFun1(inFormatString: String) { - println(ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(getConvertedFormats(Struct(inFormatString)))) - } - - @Test - fun constructorTest() { - constructorTestFun1("2s") - constructorTestFun1("2b") - constructorTestFun1("2bs") - } - - @Test - fun calcSizeTest() { - assertEquals(16, Struct("<2i4b4b").calcSize()) - assertEquals(16, Struct("h").calcSize()) - assertEquals(3, Struct(">3s").calcSize()) - assertEquals(4, Struct("!Hh").calcSize()) - - Struct("<2i4b4b").dump() - - try { - Struct("abcd") - throw Exception("should not reach here") - } catch (e: IllegalArgumentException) { - } - } - - @Test - fun integerLE() { - //int (4B) - assertTrue(Struct("<2i").pack(1, 7321)!!.contentEquals(Helper.fromHexString("01000000991c0000"))) - val ret = Struct("<2i").unpack(ByteArrayInputStream(Helper.fromHexString("01000000991c0000"))) - assertEquals(2, ret.size) - assertTrue(ret[0] is Int) - assertTrue(ret[1] is Int) - assertEquals(1, ret[0] as Int) - assertEquals(7321, ret[1] as Int) - - //unsigned int (4B) - assertTrue(Struct("2i").pack(1, 7321)!!.contentEquals(Helper.fromHexString("0000000100001c99"))) - val ret = Struct(">2i").unpack(ByteArrayInputStream(Helper.fromHexString("0000000100001c99"))) - assertEquals(1, ret[0] as Int) - assertEquals(7321, ret[1] as Int) - } - - run { - assertTrue(Struct("!i").pack(-333)!!.contentEquals(Helper.fromHexString("fffffeb3"))) - val ret2 = Struct("!i").unpack(ByteArrayInputStream(Helper.fromHexString("fffffeb3"))) - assertEquals(-333, ret2[0] as Int) - } - } - - @Test - fun byteArrayTest() { - //byte array - assertTrue(Struct("<4b").pack(byteArrayOf(-128, 2, 55, 127))!!.contentEquals(Helper.fromHexString("8002377f"))) - assertTrue(Struct("<4b").pack(intArrayOf(0, 55, 202, 0xff))!!.contentEquals(Helper.fromHexString("0037caff"))) - try { - Struct("b").pack(intArrayOf(256)) - throw Exception("should not reach here") - } catch (e: IllegalArgumentException) { - } - try { - Struct("b").pack(intArrayOf(-1)) - throw Exception("should not reach here") - } catch (e: IllegalArgumentException) { - } - } - - @Test - fun packCombinedTest() { - assertTrue(Struct("<2i4b4b").pack( - 1, 7321, byteArrayOf(1, 2, 3, 4), byteArrayOf(200.toByte(), 201.toByte(), 202.toByte(), 203.toByte()))!! - .contentEquals(Helper.fromHexString("01000000991c000001020304c8c9cacb"))) - assertTrue(Struct("<2i4b4b").pack( - 1, 7321, byteArrayOf(1, 2, 3, 4), intArrayOf(200, 201, 202, 203))!! - .contentEquals(Helper.fromHexString("01000000991c000001020304c8c9cacb"))) - } - - @Test - fun paddingTest() { - assertTrue(Struct("b2x").pack(byteArrayOf(0x13), null)!!.contentEquals(Helper.fromHexString("130000"))) - assertTrue(Struct("b2xi").pack(byteArrayOf(0x13), null, 55)!!.contentEquals(Helper.fromHexString("13000037000000"))) - } - - @Test - fun stringTest() { - Struct("5s").pack("Good".toByteArray())!!.contentEquals(Helper.fromHexString("476f6f6400")) - Struct("5s1b").pack("Good".toByteArray(), byteArrayOf(13))!!.contentEquals(Helper.fromHexString("476f6f64000d")) - } -} diff --git a/bbootimg/src/test/kotlin/init/RebootTest.kt b/bbootimg/src/test/kotlin/init/RebootTest.kt index b548b83..64c509f 100644 --- a/bbootimg/src/test/kotlin/init/RebootTest.kt +++ b/bbootimg/src/test/kotlin/init/RebootTest.kt @@ -1,5 +1,6 @@ package init +import cfig.bootloader_message.BootloaderMsg import cfig.init.Reboot import org.junit.Test import java.util.* @@ -23,17 +24,40 @@ class RebootTest { } @Test - fun fastbootd() { + fun bootloader() { Reboot.handlePowerctlMessage("reboot,bootloader") + } + + @Test + fun fastboot2bootloader() { val props = Properties() Reboot.handlePowerctlMessage("reboot,fastboot", props) + } + + @Test + fun fastbootd() { + val props = Properties() props.put(Reboot.dynamicPartitionKey, "true") Reboot.handlePowerctlMessage("reboot,fastboot", props) } + @Test + fun fastbootd2() { + val msg = BootloaderMsg() + msg.updateBootloaderMessage("boot-fastboot", "recovery", null) + msg.writeBootloaderMessage() + } + @Test fun sideload() { Reboot.handlePowerctlMessage("reboot,sideload-auto-reboot") Reboot.handlePowerctlMessage("reboot,sideload") } + + @Test + fun rescue() { + val msg = BootloaderMsg() + msg.updateBootloaderMessage("boot-rescue", "recovery", null) + msg.writeBootloaderMessage() + } } diff --git a/build.gradle b/build.gradle index 96fa81b..d15ada8 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,7 @@ if (parseGradleVersion(gradle.gradleVersion) < 5) { } def workdir = 'build/unzip_boot' +def GROUP_ANDROID = "Android" project.ext.rootWorkDir = new File(workdir).getAbsolutePath() String activeImg = "boot.img" String activePath = "/boot" @@ -49,7 +50,7 @@ if (new File("boot.img").exists()) { activePath = "/vbmeta" } project.ext.outClearIMg = new File(String.format("%s.clear", activeImg)).getAbsolutePath() -project.ext.mkbootimgBin = new File("src/mkbootimg/mkbootimg").getAbsolutePath() +project.ext.mkbootimgBin = new File("tools/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() @@ -58,49 +59,14 @@ logger.warn("Active image target: " + activeImg) // ---------------------------------------------------------------------------- // tasks // ---------------------------------------------------------------------------- -task unpack(type: JavaExec, dependsOn: ["bbootimg:jar"]) { - classpath = sourceSets.main.runtimeClasspath - main = "cfig.RKt" - classpath = files("bbootimg/build/libs/bbootimg.jar") - maxHeapSize '512m' - args "unpack", activeImg, rootProject.mkbootimgBin, rootProject.avbtool, rootProject.bootSigner, rootProject.mkbootfsBin -} - -task packClear(type: JavaExec, dependsOn: ["bbootimg:jar", "mkbootfs:mkbootfsExecutable"]) { - classpath = sourceSets.main.runtimeClasspath - main = "cfig.RKt" - classpath = files("bbootimg/build/libs/bbootimg.jar") - maxHeapSize '512m' - args "pack", activeImg, rootProject.mkbootimgBin, rootProject.avbtool, rootProject.bootSigner, rootProject.mkbootfsBin -} - -task sign(type: JavaExec, dependsOn: ["bbootimg:jar", packClear, "boot_signer:jar"]) { - classpath = sourceSets.main.runtimeClasspath - main = "cfig.RKt" - classpath = files("bbootimg/build/libs/bbootimg.jar") - maxHeapSize '4096m' - args "sign", activeImg, rootProject.mkbootimgBin, rootProject.avbtool, rootProject.bootSigner, rootProject.mkbootfsBin -} - -task signTest(type: JavaExec, dependsOn: ["boot_signer:jar"]) { - main = 'com.android.verity.BootSignature' - classpath = files("boot_signer/build/libs/boot_signer.jar") - maxHeapSize '512m' - args activePath, activeImg + '.clear', 'security/verity.pk8', 'security/verity.x509.pem', activeImg + '.signed', rootProject.mkbootfsBin -} - -task pack(dependsOn: sign) { - doLast { - println("Pack task finished: " + activeImg + ".signed") - } -} - task _setup(type: Copy) { + group GROUP_ANDROID from 'src/test/resources/boot.img' into '.' } task pull() { + group GROUP_ANDROID doFirst { println("Pulling ...") } @@ -179,6 +145,7 @@ void updateBootImage(String activeImg) { } task flash { + group GROUP_ANDROID doLast { updateBootImage(activeImg) updateBootImage("vbmeta.img") @@ -190,19 +157,22 @@ void rebootRecovery() { } task rr { + group GROUP_ANDROID doLast { rebootRecovery() } } -task u(type: JavaExec, dependsOn: ["bbootimg:jar"]) { +task unpack(type: JavaExec, dependsOn: ["bbootimg:jar"]) { + group GROUP_ANDROID main = "cfig.packable.PackableLauncherKt" classpath = files("bbootimg/build/libs/bbootimg.jar") maxHeapSize '512m' args "unpack" } -task p(type: JavaExec, dependsOn: ["bbootimg:jar", "mkbootfs:mkbootfsExecutable"]) { +task pack(type: JavaExec, dependsOn: ["bbootimg:jar", "mkbootfs:mkbootfsExecutable"]) { + group GROUP_ANDROID main = "cfig.packable.PackableLauncherKt" classpath = files("bbootimg/build/libs/bbootimg.jar") maxHeapSize '512m' diff --git a/doc/layout.md b/doc/layout.md index a6bcb01..b553866 100644 --- a/doc/layout.md +++ b/doc/layout.md @@ -46,9 +46,9 @@ |--------------------------------+--------------------------| --> 608 (0x260) | | 1024 | |--------------------------------+--------------------------| --> 1632 (0x660) - | [v1] | 4 | + | [v1] | 4 | |--------------------------------+--------------------------| --> 1636 - | [v1] | 8 | + | [v1] | 8 | |--------------------------------+--------------------------| --> 1644 |
[v1] | 4 (v1: value=1648) | | | (v2: value=1660) | @@ -112,7 +112,7 @@ | | - Header Magic "AVB0" | 4 | | | - avb_version Major | 4 | | | - avb_version Minor | 4 | - | | - authentication blob size | 8 | + | | - authentication_blob_size | 8 | | | - auxiliary blob size | 8 | | | - algorithm type | 4 | | | - hash_offset | 8 | @@ -133,14 +133,14 @@ | | - RESERVED | 80 | | |--------------------------------+-------------------------+ --> + 256 | | Authentication Blob | | - | | - Hash of Header & Aux Blob | alg.hash_num_bytes | - | | - Signature of Hash | alg.signature_num_bytes | + | | - Hash of Header & Aux Blob | alg.hash_num_bytes | --> + 256 + hash_offset + | | - Signature of Hash | alg.signature_num_bytes | --> + 256 + signature_offset | | - Padding | align by 64 | | +--------------------------------+-------------------------+ | | Auxiliary Blob | | - | | - descriptors | | --> + 256 + descriptors_offset - | | - pub key | | --> + 256 + pub_key_offset - | | - pub key meta data | | --> + 256 + pub_key_metadata_offset + | | - descriptors | | --> + 256 + authentication_blob_size + descriptors_offset + | | - pub key | | --> + 256 + authentication_blob_size + pub_key_offset + | | - pub key meta data | | --> + 256 + authentication_blob_size + pub_key_metadata_offset | | - padding | align by 64 | | +--------------------------------+-------------------------+ | | Padding | align by block_size | diff --git a/integrationTest.py b/integrationTest.py index 8bf57a4..0b4d7a3 100755 --- a/integrationTest.py +++ b/integrationTest.py @@ -52,7 +52,7 @@ def verifySingleJson(inResourceDir, inImageDir, jsonFile): subprocess.check_call("gradle pack", shell = True) for k, v in verifyItems["hash"].items(): log.info("%s : %s" % (k, v)) - unittest.TestCase().assertEqual(hashFile(k), v) + unittest.TestCase().assertEqual(v, hashFile(k)) def verifySingleDir(inResourceDir, inImageDir): resDir = inResourceDir diff --git a/src/integrationTest/resources b/src/integrationTest/resources index b33e958..42ceedb 160000 --- a/src/integrationTest/resources +++ b/src/integrationTest/resources @@ -1 +1 @@ -Subproject commit b33e958f598f8cb56df60a6f50654c09e85b4996 +Subproject commit 42ceedb9271b5ebd3cefd0815719ee5ddcd5f130 diff --git a/src/mkbootimg/mkbootimg b/tools/mkbootimg similarity index 95% rename from src/mkbootimg/mkbootimg rename to tools/mkbootimg index 92b11a5..1d91c7a 100755 --- a/src/mkbootimg/mkbootimg +++ b/tools/mkbootimg @@ -70,13 +70,15 @@ def write_header(args): raise ValueError('Boot header version %d not supported' % args.header_version) args.output.write(pack('8s', BOOT_MAGIC)) + final_ramdisk_offset = (args.base + args.ramdisk_offset) if filesize(args.ramdisk) > 0 else 0 + final_second_offset = (args.base + args.second_offset) if filesize(args.second) > 0 else 0 args.output.write(pack('10I', filesize(args.kernel), # size in bytes args.base + args.kernel_offset, # physical load addr filesize(args.ramdisk), # size in bytes - args.base + args.ramdisk_offset, # physical load addr + final_ramdisk_offset, # physical load addr filesize(args.second), # size in bytes - args.base + args.second_offset, # physical load addr + final_second_offset, # physical load addr args.base + args.tags_offset, # physical addr for kernel tags args.pagesize, # flash page size we assume args.header_version, # version of bootimage header @@ -113,6 +115,10 @@ def write_header(args): args.output.write(pack('I', BOOT_IMAGE_HEADER_V2_SIZE)) if args.header_version > 1: + + if filesize(args.dtb) == 0: + raise ValueError("DTB image must not be empty.") + 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)