Resign microdroid_vendor.img with avb_vendor_key
microdroid_vendor.img is vendor image for running Microdroid, a type of
VM run on top of Android. microdroid_vendor.img is currently stored in
host device's vendor partition. However the original key signed for
microdroid_vendor.img is not enrolled in the signing server, so it can
make possible test breakage if there's a test checking all files in the
partition is signed with trusted key.
Therefore, this is the patch to resign micrdoroid_vendor.img with
avb_vendor_key. When vendor image of host device is resigned with that
key, microdroid_vendor.img would be resigned as well with the same key.
Bug: 285855442
Test: First, for testing, modify the script to skip all files except
VENDOR/etc/avf/microdroid/microdroid_vendor.img in ProcessTargetFiles.
Second, run following commands and check if script doesn't throw any
error until ProcessTargetFiles ends.
- sign_target_files_apks --avb_vendor_key external/avb/test/data/testkey_rsa2048.pem --avb_vendor_algorithm SHA256_RSA2048 <source_zip_file> <target_zip_file>
- sign_target_files_apks --avb_vendor_key external/avb/test/data/testkey_rsa4096.pem --avb_vendor_algorithm SHA256_RSA4096 <source_zip_file> <target_zip_file>
- sign_target_files_apks --avb_vendor_key external/avb/test/data/testkey_rsa8192.pem --avb_vendor_algorithm SHA256_RSA8192 <source_zip_file> <target_zip_file>
Third, `unzip <target_zip_file>` and do `avbtool info_image`
Change-Id: I5337f61ab9eca7e6d0f92860486bc820b6e09eac
diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py
index bf69dec..52d95d8 100755
--- a/tools/releasetools/sign_target_files_apks.py
+++ b/tools/releasetools/sign_target_files_apks.py
@@ -795,6 +795,16 @@
# Copy it verbatim if we allow the file to exist.
common.ZipWriteStr(output_tf_zip, out_info, data)
+ # Sign microdroid_vendor.img.
+ elif filename == "VENDOR/etc/avf/microdroid/microdroid_vendor.img":
+ vendor_key = OPTIONS.avb_keys.get("vendor")
+ vendor_algorithm = OPTIONS.avb_algorithms.get("vendor")
+ with tempfile.NamedTemporaryFile() as image:
+ image.write(data)
+ image.flush()
+ ReplaceKeyInAvbHashtreeFooter(image, vendor_key, vendor_algorithm,
+ misc_info)
+ common.ZipWrite(output_tf_zip, image.name, filename)
# A non-APK file; copy it verbatim.
else:
common.ZipWriteStr(output_tf_zip, out_info, data)
@@ -812,6 +822,106 @@
# Write back misc_info with the latest values.
ReplaceMiscInfoTxt(input_tf_zip, output_tf_zip, misc_info)
+def ReplaceKeyInAvbHashtreeFooter(image, new_key, new_algorithm, misc_info):
+ # Get avb information about the image by parsing avbtool info_image.
+ def GetAvbInfo(avbtool, image_name):
+ # Get information with raw string by `avbtool info_image`.
+ info_raw = common.RunAndCheckOutput([
+ avbtool, 'info_image',
+ '--image', image_name
+ ])
+
+ # line_matcher is for parsing each output line of `avbtool info_image`.
+ # example string input: " Hash Algorithm: sha1"
+ # example matched input: (" ", "Hash Algorithm", "sha1")
+ line_matcher = re.compile(r'^(\s*)([^:]+):\s*(.*)$')
+ # prop_matcher is for parsing value part of 'Prop' in `avbtool info_image`.
+ # example string input: "example_prop_key -> 'example_prop_value'"
+ # example matched output: ("example_prop_key", "example_prop_value")
+ prop_matcher = re.compile(r"(.+)\s->\s'(.+)'")
+ info = {}
+ indent_stack = [[-1, info]]
+ for line_info_raw in info_raw.split('\n'):
+ # Parse the line
+ line_info_parsed = line_matcher.match(line_info_raw)
+ if not line_info_parsed:
+ continue
+ indent = len(line_info_parsed.group(1))
+ key = line_info_parsed.group(2).strip()
+ value = line_info_parsed.group(3).strip()
+
+ # Pop indentation stack
+ while indent <= indent_stack[-1][0]:
+ del indent_stack[-1]
+
+ # Insert information into 'info'.
+ cur_info = indent_stack[-1][1]
+ if value == "":
+ if key == "Descriptors":
+ empty_list = []
+ cur_info[key] = empty_list
+ indent_stack.append([indent, empty_list])
+ else:
+ empty_dict = {}
+ cur_info.append({key:empty_dict})
+ indent_stack.append([indent, empty_dict])
+ elif key == "Prop":
+ prop_parsed = prop_matcher.match(value)
+ if not prop_parsed:
+ raise ValueError(
+ "Failed to parse prop while getting avb information.")
+ cur_info.append({key:{prop_parsed.group(1):prop_parsed.group(2)}})
+ else:
+ cur_info[key] = value
+
+ return info
+
+ # Get hashtree descriptor from info
+ def GetAvbHashtreeDescriptor(avb_info):
+ hashtree_descriptors = tuple(filter(lambda x: "Hashtree descriptor" in x,
+ info.get('Descriptors')))
+ if len(hashtree_descriptors) != 1:
+ raise ValueError("The number of hashtree descriptor is not 1.")
+ return hashtree_descriptors[0]["Hashtree descriptor"]
+
+ # Get avb info
+ avbtool = misc_info['avb_avbtool']
+ info = GetAvbInfo(avbtool, image.name)
+ hashtree_descriptor = GetAvbHashtreeDescriptor(info)
+
+ # Generate command
+ cmd = [avbtool, 'add_hashtree_footer',
+ '--key', new_key,
+ '--algorithm', new_algorithm,
+ '--partition_name', hashtree_descriptor.get("Partition Name"),
+ '--partition_size', info.get("Image size").removesuffix(" bytes"),
+ '--hash_algorithm', hashtree_descriptor.get("Hash Algorithm"),
+ '--salt', hashtree_descriptor.get("Salt"),
+ '--do_not_generate_fec',
+ '--image', image.name
+ ]
+
+ # Append properties into command
+ props = map(lambda x: x.get("Prop"), filter(lambda x: "Prop" in x,
+ info.get('Descriptors')))
+ for prop_wrapped in props:
+ prop = tuple(prop_wrapped.items())
+ if len(prop) != 1:
+ raise ValueError("The number of property is not 1.")
+ cmd.append('--prop')
+ cmd.append(prop[0][0] + ':' + prop[0][1])
+
+ # Replace Hashtree Footer with new key
+ common.RunAndCheckOutput(cmd)
+
+ # Check root digest is not changed
+ new_info = GetAvbInfo(avbtool, image.name)
+ new_hashtree_descriptor = GetAvbHashtreeDescriptor(info)
+ root_digest = hashtree_descriptor.get("Root Digest")
+ new_root_digest = new_hashtree_descriptor.get("Root Digest")
+ assert root_digest == new_root_digest, \
+ ("Root digest in hashtree descriptor shouldn't be changed. Old: {}, New: "
+ "{}").format(root_digest, new_root_digest)
def ReplaceCerts(data):
"""Replaces all the occurences of X.509 certs with the new ones.