From c098e9efd989a46d5d1aa625bc899536de15eaaa Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Thu, 7 Jan 2016 13:03:56 -0800 Subject: [PATCH] Generate OTA packages for A/B update. It calls brillo_update_payload to generate the payload for A/B update. And packages the payload according to Android OTA package format. Note that it only supports generating full/incremental OTAs with this CL. Signing for release may not work properly at the moment. Bug: 25715402 Change-Id: I4ac8505bacad28a572a9320dc8b52dd0f1ce47f5 --- tools/releasetools/ota_from_target_files.py | 162 ++++++++++++++++++-- 1 file changed, 153 insertions(+), 9 deletions(-) diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py index d0636b672..a56776011 100755 --- a/tools/releasetools/ota_from_target_files.py +++ b/tools/releasetools/ota_from_target_files.py @@ -109,6 +109,7 @@ if sys.hexversion < 0x02070000: import multiprocessing import os +import subprocess import tempfile import zipfile @@ -1078,6 +1079,124 @@ def WriteVerifyPackage(input_zip, output_zip): WriteMetadata(metadata, output_zip) +def WriteABOTAPackageWithBrilloScript(target_file, output_file, + source_file=None): + """Generate an Android OTA package that has A/B update payload.""" + + # Setup signing keys. + if OPTIONS.package_key is None: + OPTIONS.package_key = OPTIONS.info_dict.get( + "default_system_dev_certificate", + "build/target/product/security/testkey") + + # A/B updater expects key in RSA format. + cmd = ["openssl", "pkcs8", + "-in", OPTIONS.package_key + OPTIONS.private_key_suffix, + "-inform", "DER", "-nocrypt"] + rsa_key = common.MakeTempFile(prefix="key-", suffix=".key") + cmd.extend(["-out", rsa_key]) + p1 = common.Run(cmd, stdout=subprocess.PIPE) + p1.wait() + assert p1.returncode == 0, "openssl pkcs8 failed" + + # Stage the output zip package for signing. + temp_zip_file = tempfile.NamedTemporaryFile() + output_zip = zipfile.ZipFile(temp_zip_file, "w", + compression=zipfile.ZIP_DEFLATED) + + # Metadata to comply with Android OTA package format. + oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties", None) + oem_dict = None + if oem_props: + if OPTIONS.oem_source is None: + raise common.ExternalError("OEM source required for this build") + oem_dict = common.LoadDictionaryFromLines( + open(OPTIONS.oem_source).readlines()) + + metadata = { + "post-build": CalculateFingerprint(oem_props, oem_dict, + OPTIONS.info_dict), + "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict, + OPTIONS.info_dict), + "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict), + } + + if source_file is not None: + metadata["pre-build"] = CalculateFingerprint(oem_props, oem_dict, + OPTIONS.source_info_dict) + + # 1. Generate payload. + payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin") + cmd = ["brillo_update_payload", "generate", + "--payload", payload_file, + "--target_image", target_file] + if source_file is not None: + cmd.extend(["--source_image", source_file]) + p1 = common.Run(cmd, stdout=subprocess.PIPE) + p1.wait() + assert p1.returncode == 0, "brillo_update_payload generate failed" + + # 2. Generate hashes of the payload and metadata files. + payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin") + metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin") + cmd = ["brillo_update_payload", "hash", + "--unsigned_payload", payload_file, + "--signature_size", "256", + "--metadata_hash_file", metadata_sig_file, + "--payload_hash_file", payload_sig_file] + p1 = common.Run(cmd, stdout=subprocess.PIPE) + p1.wait() + assert p1.returncode == 0, "brillo_update_payload hash failed" + + # 3. Sign the hashes and insert them back into the payload file. + signed_payload_sig_file = common.MakeTempFile(prefix="signed-sig-", + suffix=".bin") + signed_metadata_sig_file = common.MakeTempFile(prefix="signed-sig-", + suffix=".bin") + # 3a. Sign the payload hash. + cmd = ["openssl", "pkeyutl", "-sign", + "-inkey", rsa_key, + "-pkeyopt", "digest:sha256", + "-in", payload_sig_file, + "-out", signed_payload_sig_file] + p1 = common.Run(cmd, stdout=subprocess.PIPE) + p1.wait() + assert p1.returncode == 0, "openssl sign payload failed" + + # 3b. Sign the metadata hash. + cmd = ["openssl", "pkeyutl", "-sign", + "-inkey", rsa_key, + "-pkeyopt", "digest:sha256", + "-in", metadata_sig_file, + "-out", signed_metadata_sig_file] + p1 = common.Run(cmd, stdout=subprocess.PIPE) + p1.wait() + assert p1.returncode == 0, "openssl sign metadata failed" + + # 3c. Insert the signatures back into the payload file. + signed_payload_file = common.MakeTempFile(prefix="signed-payload-", + suffix=".bin") + cmd = ["brillo_update_payload", "sign", + "--unsigned_payload", payload_file, + "--payload", signed_payload_file, + "--signature_size", "256", + "--metadata_signature_file", signed_metadata_sig_file, + "--payload_signature_file", signed_payload_sig_file] + p1 = common.Run(cmd, stdout=subprocess.PIPE) + p1.wait() + assert p1.returncode == 0, "brillo_update_payload sign failed" + + # Add the signed payload file into the zip. + common.ZipWrite(output_zip, signed_payload_file, arcname="payload.bin", + compress_type=zipfile.ZIP_STORED) + WriteMetadata(metadata, output_zip) + + # Sign the whole package to comply with the Android OTA package format. + common.ZipClose(output_zip) + SignOutput(temp_zip_file.name, output_file) + temp_zip_file.close() + + class FileDifference(object): def __init__(self, partition, source_zip, target_zip, output_zip): self.deferred_patch_list = None @@ -1683,6 +1802,37 @@ def main(argv): common.Usage(__doc__) sys.exit(1) + # Load the dict file from the zip directly to have a peek at the OTA type. + # For packages using A/B update, unzipping is not needed. + input_zip = zipfile.ZipFile(args[0], "r") + OPTIONS.info_dict = common.LoadInfoDict(input_zip) + common.ZipClose(input_zip) + + ab_update = OPTIONS.info_dict.get("ab_update") == "true" + + if ab_update: + if OPTIONS.incremental_source is not None: + OPTIONS.target_info_dict = OPTIONS.info_dict + source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r") + OPTIONS.source_info_dict = common.LoadInfoDict(source_zip) + common.ZipClose(source_zip) + + if OPTIONS.verbose: + print "--- target info ---" + common.DumpInfoDict(OPTIONS.info_dict) + + if OPTIONS.incremental_source is not None: + print "--- source info ---" + common.DumpInfoDict(OPTIONS.source_info_dict) + + WriteABOTAPackageWithBrilloScript( + target_file=args[0], + output_file=args[1], + source_file=OPTIONS.incremental_source) + + print "done." + return + if OPTIONS.extra_script is not None: OPTIONS.extra_script = open(OPTIONS.extra_script).read() @@ -1714,9 +1864,7 @@ def main(argv): if OPTIONS.device_specific is not None: OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific) - ab_update = OPTIONS.info_dict.get("ab_update") == "true" - - if OPTIONS.info_dict.get("no_recovery") == "true" and not ab_update: + if OPTIONS.info_dict.get("no_recovery") == "true": raise common.ExternalError( "--- target build has specified no recovery ---") @@ -1740,7 +1888,7 @@ def main(argv): # Non A/B OTAs rely on /cache partition to store temporary files. cache_size = OPTIONS.info_dict.get("cache_size", None) - if cache_size is None and not ab_update: + if cache_size is None: print "--- can't determine the cache partition size ---" OPTIONS.cache_size = cache_size @@ -1750,11 +1898,7 @@ def main(argv): # Generate a full OTA. elif OPTIONS.incremental_source is None: - if ab_update: - # TODO: Pending for b/25715402. - pass - else: - WriteFullOTAPackage(input_zip, output_zip) + WriteFullOTAPackage(input_zip, output_zip) # Generate an incremental OTA. It will fall back to generate a full OTA on # failure unless no_fallback_to_full is specified.