def test_LoadInfoDict_missingMetaMiscInfoTxt(self): target_files = self._test_LoadInfoDict_createTargetFiles( self.INFO_DICT_DEFAULT, 'BOOT/RAMDISK/system/etc/recovery.fstab') common.ZipDelete(target_files, 'META/misc_info.txt') with zipfile.ZipFile(target_files, 'r') as target_files_zip: self.assertRaises(ValueError, common.LoadInfoDict, target_files_zip)
def RepackApexPayload(self, payload_dir, payload_key, signing_args=None): """Rebuilds the apex file with the updated payload directory.""" apex_dir = common.MakeTempDir() # Extract the apex file and reuse its meta files as repack parameters. common.UnzipToDir(self.apex_path, apex_dir) arguments_dict = { 'manifest': os.path.join(apex_dir, 'apex_manifest.pb'), 'build_info': os.path.join(apex_dir, 'apex_build_info.pb'), 'key': payload_key, } for filename in arguments_dict.values(): assert os.path.exists(filename), 'file {} not found'.format( filename) # The repack process will add back these files later in the payload image. for name in ['apex_manifest.pb', 'apex_manifest.json', 'lost+found']: path = os.path.join(payload_dir, name) if os.path.isfile(path): os.remove(path) elif os.path.isdir(path): shutil.rmtree(path) # TODO(xunchang) the signing process can be improved by using # '--unsigned_payload_only'. But we need to parse the vbmeta earlier for # the signing arguments, e.g. algorithm, salt, etc. payload_img = os.path.join(apex_dir, APEX_PAYLOAD_IMAGE) generate_image_cmd = [ 'apexer', '--force', '--payload_only', '--do_not_check_keyname', '--apexer_tool_path', os.getenv('PATH') ] for key, val in arguments_dict.items(): generate_image_cmd.extend(['--' + key, val]) # Add quote to the signing_args as we will pass # --signing_args "--signing_helper_with_files=%path" to apexer if signing_args: generate_image_cmd.extend( ['--signing_args', '"{}"'.format(signing_args)]) # optional arguments for apex repacking manifest_json = os.path.join(apex_dir, 'apex_manifest.json') if os.path.exists(manifest_json): generate_image_cmd.extend(['--manifest_json', manifest_json]) generate_image_cmd.extend([payload_dir, payload_img]) if OPTIONS.verbose: generate_image_cmd.append('-v') common.RunAndCheckOutput(generate_image_cmd) # Add the payload image back to the apex file. common.ZipDelete(self.apex_path, APEX_PAYLOAD_IMAGE) with zipfile.ZipFile(self.apex_path, 'a', allowZip64=True) as output_apex: common.ZipWrite(output_apex, payload_img, APEX_PAYLOAD_IMAGE, compress_type=zipfile.ZIP_STORED) return self.apex_path
def SignApex(apex_data, payload_key, container_key, container_pw, codename_to_api_level_map, signing_args=None): """Signs the current APEX with the given payload/container keys. Args: apex_data: Raw APEX data. payload_key: The path to payload signing key (w/o extension). container_key: The path to container signing key (w/o extension). container_pw: The matching password of the container_key, or None. codename_to_api_level_map: A dict that maps from codename to API level. signing_args: Additional args to be passed to the payload signer. Returns: (signed_apex, payload_key_name): signed_apex is the path to the signed APEX file; payload_key_name is a str of the payload signing key name (e.g. com.android.tzdata). """ apex_file = common.MakeTempFile(prefix='apex-', suffix='.apex') with open(apex_file, 'wb') as apex_fp: apex_fp.write(apex_data) APEX_PAYLOAD_IMAGE = 'apex_payload.img' # Signing an APEX is a two step process. # 1. Extract and sign the APEX_PAYLOAD_IMAGE entry with the given payload_key. payload_dir = common.MakeTempDir(prefix='apex-payload-') with zipfile.ZipFile(apex_file) as apex_fd: payload_file = apex_fd.extract(APEX_PAYLOAD_IMAGE, payload_dir) payload_info = apex_utils.ParseApexPayloadInfo(payload_file) apex_utils.SignApexPayload(payload_file, payload_key, payload_info['apex.key'], payload_info['Algorithm'], payload_info['Salt'], signing_args) common.ZipDelete(apex_file, APEX_PAYLOAD_IMAGE) apex_zip = zipfile.ZipFile(apex_file, 'a') common.ZipWrite(apex_zip, payload_file, arcname=APEX_PAYLOAD_IMAGE) common.ZipClose(apex_zip) # 2. Sign the overall APEX container with container_key. signed_apex = common.MakeTempFile(prefix='apex-container-', suffix='.apex') common.SignFile(apex_file, signed_apex, container_key, container_pw, codename_to_api_level_map=codename_to_api_level_map) signed_and_aligned_apex = common.MakeTempFile(prefix='apex-container-', suffix='.apex') common.RunAndCheckOutput( ['zipalign', '-f', '4096', signed_apex, signed_and_aligned_apex]) return (signed_and_aligned_apex, payload_info['apex.key'])
def test_GetTargetFilesZipForSecondaryImages_withoutRadioImages(self): input_file = construct_target_files(secondary=True) common.ZipDelete(input_file, 'RADIO/bootloader.img') common.ZipDelete(input_file, 'RADIO/modem.img') target_file = GetTargetFilesZipForSecondaryImages(input_file) with zipfile.ZipFile(target_file) as verify_zip: namelist = verify_zip.namelist() self.assertIn('META/ab_partitions.txt', namelist) self.assertIn('IMAGES/system.img', namelist) self.assertIn(POSTINSTALL_CONFIG, namelist) self.assertNotIn('IMAGES/boot.img', namelist) self.assertNotIn('IMAGES/system_other.img', namelist) self.assertNotIn('IMAGES/system.map', namelist) self.assertNotIn('RADIO/bootloader.img', namelist) self.assertNotIn('RADIO/modem.img', namelist)
def test_ZipDelete(self): zip_file = tempfile.NamedTemporaryFile(delete=False, suffix='.zip') output_zip = zipfile.ZipFile(zip_file.name, 'w', compression=zipfile.ZIP_DEFLATED) with tempfile.NamedTemporaryFile() as entry_file: entry_file.write(os.urandom(1024)) common.ZipWrite(output_zip, entry_file.name, arcname='Test1') common.ZipWrite(output_zip, entry_file.name, arcname='Test2') common.ZipWrite(output_zip, entry_file.name, arcname='Test3') common.ZipClose(output_zip) zip_file.close() try: common.ZipDelete(zip_file.name, 'Test2') with zipfile.ZipFile(zip_file.name, 'r') as check_zip: entries = check_zip.namelist() self.assertTrue('Test1' in entries) self.assertFalse('Test2' in entries) self.assertTrue('Test3' in entries) self.assertRaises(AssertionError, common.ZipDelete, zip_file.name, 'Test2') with zipfile.ZipFile(zip_file.name, 'r') as check_zip: entries = check_zip.namelist() self.assertTrue('Test1' in entries) self.assertFalse('Test2' in entries) self.assertTrue('Test3' in entries) common.ZipDelete(zip_file.name, ['Test3']) with zipfile.ZipFile(zip_file.name, 'r') as check_zip: entries = check_zip.namelist() self.assertTrue('Test1' in entries) self.assertFalse('Test2' in entries) self.assertFalse('Test3' in entries) common.ZipDelete(zip_file.name, ['Test1', 'Test2']) with zipfile.ZipFile(zip_file.name, 'r') as check_zip: entries = check_zip.namelist() self.assertFalse('Test1' in entries) self.assertFalse('Test2' in entries) self.assertFalse('Test3' in entries) finally: os.remove(zip_file.name)
def ReplaceUpdatedFiles(zip_filename, files_list): """Updates all the ZIP entries listed in files_list. For now the list includes META/care_map.pb, and the related files under SYSTEM/ after rebuilding recovery. """ common.ZipDelete(zip_filename, files_list) output_zip = zipfile.ZipFile(zip_filename, "a", compression=zipfile.ZIP_DEFLATED, allowZip64=True) for item in files_list: file_path = os.path.join(OPTIONS.input_tmp, item) assert os.path.exists(file_path) common.ZipWrite(output_zip, file_path, arcname=item) common.ZipClose(output_zip)
def GetTargetFilesZipWithoutPostinstallConfig(input_file): """Returns a target-files.zip that's not containing postinstall_config.txt. This allows brillo_update_payload script to skip writing all the postinstall hooks in the generated payload. The input target-files.zip file will be duplicated, with 'META/postinstall_config.txt' skipped. If input_file doesn't contain the postinstall_config.txt entry, the input file will be returned. Args: input_file: The input target-files.zip filename. Returns: The filename of target-files.zip that doesn't contain postinstall config. """ # We should only make a copy if postinstall_config entry exists. with zipfile.ZipFile(input_file, 'r') as input_zip: if POSTINSTALL_CONFIG not in input_zip.namelist(): return input_file target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip") shutil.copyfile(input_file, target_file) common.ZipDelete(target_file, POSTINSTALL_CONFIG) return target_file
def SignUncompressedApex(avbtool, apex_file, payload_key, container_key, container_pw, apk_keys, codename_to_api_level_map, no_hashtree, signing_args=None): """Signs the current uncompressed APEX with the given payload/container keys. Args: apex_file: Uncompressed APEX file. payload_key: The path to payload signing key (w/ extension). container_key: The path to container signing key (w/o extension). container_pw: The matching password of the container_key, or None. apk_keys: A dict that holds the signing keys for apk files. codename_to_api_level_map: A dict that maps from codename to API level. no_hashtree: Don't include hashtree in the signed APEX. signing_args: Additional args to be passed to the payload signer. Returns: The path to the signed APEX file. """ # 1. Extract the apex payload image and sign the containing apk files. Repack # the apex file after signing. apk_signer = ApexApkSigner(apex_file, container_pw, codename_to_api_level_map) apex_file = apk_signer.ProcessApexFile(apk_keys, payload_key, signing_args) # 2a. Extract and sign the APEX_PAYLOAD_IMAGE entry with the given # payload_key. payload_dir = common.MakeTempDir(prefix='apex-payload-') with zipfile.ZipFile(apex_file) as apex_fd: payload_file = apex_fd.extract(APEX_PAYLOAD_IMAGE, payload_dir) zip_items = apex_fd.namelist() payload_info = ParseApexPayloadInfo(avbtool, payload_file) if no_hashtree is None: no_hashtree = payload_info.get("Tree Size", 0) == 0 SignApexPayload( avbtool, payload_file, payload_key, payload_info['apex.key'], payload_info['Algorithm'], payload_info['Salt'], payload_info['Hash Algorithm'], no_hashtree, signing_args) # 2b. Update the embedded payload public key. payload_public_key = common.ExtractAvbPublicKey(avbtool, payload_key) common.ZipDelete(apex_file, APEX_PAYLOAD_IMAGE) if APEX_PUBKEY in zip_items: common.ZipDelete(apex_file, APEX_PUBKEY) apex_zip = zipfile.ZipFile(apex_file, 'a', allowZip64=True) common.ZipWrite(apex_zip, payload_file, arcname=APEX_PAYLOAD_IMAGE) common.ZipWrite(apex_zip, payload_public_key, arcname=APEX_PUBKEY) common.ZipClose(apex_zip) # 3. Sign the APEX container with container_key. signed_apex = common.MakeTempFile(prefix='apex-container-', suffix='.apex') # Specify the 4K alignment when calling SignApk. extra_signapk_args = OPTIONS.extra_signapk_args[:] extra_signapk_args.extend(['-a', '4096', '--align-file-size']) password = container_pw.get(container_key) if container_pw else None common.SignFile( apex_file, signed_apex, container_key, password, codename_to_api_level_map=codename_to_api_level_map, extra_signapk_args=extra_signapk_args) return signed_apex
def test_GetTargetFilesZipWithoutPostinstallConfig_missingEntry(self): input_file = construct_target_files() common.ZipDelete(input_file, POSTINSTALL_CONFIG) target_file = GetTargetFilesZipWithoutPostinstallConfig(input_file) with zipfile.ZipFile(target_file) as verify_zip: self.assertNotIn(POSTINSTALL_CONFIG, verify_zip.namelist())
def test_Generate_invalidInput(self): target_file = construct_target_files() common.ZipDelete(target_file, 'IMAGES/vendor.img') payload = Payload() self.assertRaises(common.ExternalError, payload.Generate, target_file)
def GetTargetFilesZipForRetrofitDynamicPartitions(input_file, super_block_devices, dynamic_partition_list): """Returns a target-files.zip for retrofitting dynamic partitions. This allows brillo_update_payload to generate an OTA based on the exact bits on the block devices. Postinstall is disabled. Args: input_file: The input target-files.zip filename. super_block_devices: The list of super block devices dynamic_partition_list: The list of dynamic partitions Returns: The filename of target-files.zip with *.img replaced with super_*.img for each block device in super_block_devices. """ assert super_block_devices, "No super_block_devices are specified." replace = { 'OTA/super_{}.img'.format(dev): 'IMAGES/{}.img'.format(dev) for dev in super_block_devices } target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip") shutil.copyfile(input_file, target_file) with zipfile.ZipFile(input_file) as input_zip: namelist = input_zip.namelist() input_tmp = common.UnzipTemp(input_file, RETROFIT_DAP_UNZIP_PATTERN) # Remove partitions from META/ab_partitions.txt that is in # dynamic_partition_list but not in super_block_devices so that # brillo_update_payload won't generate update for those logical partitions. ab_partitions_file = os.path.join(input_tmp, *AB_PARTITIONS.split('/')) with open(ab_partitions_file) as f: ab_partitions_lines = f.readlines() ab_partitions = [line.strip() for line in ab_partitions_lines] # Assert that all super_block_devices are in ab_partitions super_device_not_updated = [ partition for partition in super_block_devices if partition not in ab_partitions ] assert not super_device_not_updated, \ "{} is in super_block_devices but not in {}".format( super_device_not_updated, AB_PARTITIONS) # ab_partitions -= (dynamic_partition_list - super_block_devices) new_ab_partitions = common.MakeTempFile(prefix="ab_partitions", suffix=".txt") with open(new_ab_partitions, 'w') as f: for partition in ab_partitions: if (partition in dynamic_partition_list and partition not in super_block_devices): logger.info("Dropping %s from ab_partitions.txt", partition) continue f.write(partition + "\n") to_delete = [AB_PARTITIONS] # Always skip postinstall for a retrofit update. to_delete += [POSTINSTALL_CONFIG] # Delete dynamic_partitions_info.txt so that brillo_update_payload thinks this # is a regular update on devices without dynamic partitions support. to_delete += [DYNAMIC_PARTITION_INFO] # Remove the existing partition images as well as the map files. to_delete += list(replace.values()) to_delete += ['IMAGES/{}.map'.format(dev) for dev in super_block_devices] common.ZipDelete(target_file, to_delete) target_zip = zipfile.ZipFile(target_file, 'a', allowZip64=True) # Write super_{foo}.img as {foo}.img. for src, dst in replace.items(): assert src in namelist, \ 'Missing {} in {}; {} cannot be written'.format(src, input_file, dst) unzipped_file = os.path.join(input_tmp, *src.split('/')) common.ZipWrite(target_zip, unzipped_file, arcname=dst) # Write new ab_partitions.txt file common.ZipWrite(target_zip, new_ab_partitions, arcname=AB_PARTITIONS) common.ZipClose(target_zip) return target_file
def SignApex(apex_data, payload_key, container_key, container_pw, codename_to_api_level_map, signing_args=None): """Signs the current APEX with the given payload/container keys. Args: apex_data: Raw APEX data. payload_key: The path to payload signing key (w/ extension). container_key: The path to container signing key (w/o extension). container_pw: The matching password of the container_key, or None. codename_to_api_level_map: A dict that maps from codename to API level. signing_args: Additional args to be passed to the payload signer. Returns: The path to the signed APEX file. """ apex_file = common.MakeTempFile(prefix='apex-', suffix='.apex') with open(apex_file, 'wb') as apex_fp: apex_fp.write(apex_data) APEX_PAYLOAD_IMAGE = 'apex_payload.img' APEX_PUBKEY = 'apex_pubkey' # 1a. Extract and sign the APEX_PAYLOAD_IMAGE entry with the given # payload_key. payload_dir = common.MakeTempDir(prefix='apex-payload-') with zipfile.ZipFile(apex_file) as apex_fd: payload_file = apex_fd.extract(APEX_PAYLOAD_IMAGE, payload_dir) zip_items = apex_fd.namelist() payload_info = ParseApexPayloadInfo(payload_file) SignApexPayload(payload_file, payload_key, payload_info['apex.key'], payload_info['Algorithm'], payload_info['Salt'], signing_args) # 1b. Update the embedded payload public key. payload_public_key = common.ExtractAvbPublicKey(payload_key) common.ZipDelete(apex_file, APEX_PAYLOAD_IMAGE) if APEX_PUBKEY in zip_items: common.ZipDelete(apex_file, APEX_PUBKEY) apex_zip = zipfile.ZipFile(apex_file, 'a') common.ZipWrite(apex_zip, payload_file, arcname=APEX_PAYLOAD_IMAGE) common.ZipWrite(apex_zip, payload_public_key, arcname=APEX_PUBKEY) common.ZipClose(apex_zip) # 2. Align the files at page boundary (same as in apexer). aligned_apex = common.MakeTempFile(prefix='apex-container-', suffix='.apex') common.RunAndCheckOutput( ['zipalign', '-f', '4096', apex_file, aligned_apex]) # 3. Sign the APEX container with container_key. signed_apex = common.MakeTempFile(prefix='apex-container-', suffix='.apex') # Specify the 4K alignment when calling SignApk. extra_signapk_args = OPTIONS.extra_signapk_args[:] extra_signapk_args.extend(['-a', '4096']) common.SignFile(aligned_apex, signed_apex, container_key, container_pw, codename_to_api_level_map=codename_to_api_level_map, extra_signapk_args=extra_signapk_args) return signed_apex