Sign OTA packages inside target_files during signing

Test: th
Bug: 293313353
Change-Id: Ifd5dd08153c5970dac8166808173f7dfbbb3411d
diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp
index ee266b7..bd8ce14 100644
--- a/tools/releasetools/Android.bp
+++ b/tools/releasetools/Android.bp
@@ -333,6 +333,7 @@
     srcs: [
         "ota_utils.py",
         "payload_signer.py",
+        "ota_signing_utils.py",
     ],
     libs: [
         "releasetools_common",
@@ -348,7 +349,6 @@
     },
     srcs: [
         "merge_ota.py",
-        "ota_signing_utils.py",
     ],
     libs: [
         "ota_metadata_proto",
@@ -501,7 +501,6 @@
     name: "ota_from_raw_img",
     srcs: [
         "ota_from_raw_img.py",
-        "ota_signing_utils.py",
     ],
     main: "ota_from_raw_img.py",
     defaults: [
@@ -552,6 +551,8 @@
     defaults: ["releasetools_binary_defaults"],
     srcs: [
         "sign_target_files_apks.py",
+        "payload_signer.py",
+        "ota_signing_utils.py",
     ],
     libs: [
         "releasetools_add_img_to_target_files",
@@ -615,7 +616,6 @@
         "sign_target_files_apks.py",
         "validate_target_files.py",
         "merge_ota.py",
-        "ota_signing_utils.py",
         ":releasetools_merge_sources",
         ":releasetools_merge_tests",
 
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 8ce6083..7f720f6 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -3120,6 +3120,34 @@
   zip_file.writestr(zinfo, data)
   zipfile.ZIP64_LIMIT = saved_zip64_limit
 
+def ZipExclude(input_zip, output_zip, entries, force=False):
+  """Deletes entries from a ZIP file.
+
+  Args:
+    zip_filename: The name of the ZIP file.
+    entries: The name of the entry, or the list of names to be deleted.
+  """
+  if isinstance(entries, str):
+    entries = [entries]
+  # If list is empty, nothing to do
+  if not entries:
+    shutil.copy(input_zip, output_zip)
+    return
+
+  with zipfile.ZipFile(input_zip, 'r') as zin:
+    if not force and len(set(zin.namelist()).intersection(entries)) == 0:
+      raise ExternalError(
+          "Failed to delete zip entries, name not matched: %s" % entries)
+
+    fd, new_zipfile = tempfile.mkstemp(dir=os.path.dirname(input_zip))
+    os.close(fd)
+    cmd = ["zip2zip", "-i", input_zip, "-o", new_zipfile]
+    for entry in entries:
+      cmd.append("-x")
+      cmd.append(entry)
+    RunAndCheckOutput(cmd)
+  os.replace(new_zipfile, output_zip)
+
 
 def ZipDelete(zip_filename, entries, force=False):
   """Deletes entries from a ZIP file.
@@ -3134,20 +3162,7 @@
   if not entries:
     return
 
-  with zipfile.ZipFile(zip_filename, 'r') as zin:
-    if not force and len(set(zin.namelist()).intersection(entries)) == 0:
-      raise ExternalError(
-          "Failed to delete zip entries, name not matched: %s" % entries)
-
-    fd, new_zipfile = tempfile.mkstemp(dir=os.path.dirname(zip_filename))
-    os.close(fd)
-    cmd = ["zip2zip", "-i", zip_filename, "-o", new_zipfile]
-    for entry in entries:
-      cmd.append("-x")
-      cmd.append(entry)
-    RunAndCheckOutput(cmd)
-
-  os.replace(new_zipfile, zip_filename)
+  ZipExclude(zip_filename, zip_filename, entries, force)
 
 
 def ZipClose(zip_file):
diff --git a/tools/releasetools/ota_utils.py b/tools/releasetools/ota_utils.py
index 0a6ff39..ddd2d36 100644
--- a/tools/releasetools/ota_utils.py
+++ b/tools/releasetools/ota_utils.py
@@ -27,7 +27,8 @@
                     ZipWriteStr, BuildInfo, LoadDictionaryFromFile,
                     SignFile, PARTITIONS_WITH_BUILD_PROP, PartitionBuildProps,
                     GetRamdiskFormat, ParseUpdateEngineConfig)
-from payload_signer import PayloadSigner
+import payload_signer
+from payload_signer import PayloadSigner, AddSigningArgumentParse, GeneratePayloadProperties
 
 
 logger = logging.getLogger(__name__)
@@ -785,8 +786,8 @@
 class PayloadGenerator(object):
   """Manages the creation and the signing of an A/B OTA Payload."""
 
-  PAYLOAD_BIN = 'payload.bin'
-  PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
+  PAYLOAD_BIN = payload_signer.PAYLOAD_BIN
+  PAYLOAD_PROPERTIES_TXT = payload_signer.PAYLOAD_PROPERTIES_TXT
   SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin'
   SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
 
@@ -905,12 +906,7 @@
     """
     assert self.payload_file is not None
     # 4. Dump the signed payload properties.
-    properties_file = common.MakeTempFile(prefix="payload-properties-",
-                                          suffix=".txt")
-    cmd = ["delta_generator",
-           "--in_file=" + self.payload_file,
-           "--properties_file=" + properties_file]
-    self._Run(cmd)
+    properties_file = GeneratePayloadProperties(self.payload_file)
 
 
     with open(properties_file, "a") as f:
diff --git a/tools/releasetools/payload_signer.py b/tools/releasetools/payload_signer.py
index a5d09e1..e85d64c 100644
--- a/tools/releasetools/payload_signer.py
+++ b/tools/releasetools/payload_signer.py
@@ -17,7 +17,12 @@
 import common
 import logging
 import shlex
+import argparse
+import tempfile
+import zipfile
+import shutil
 from common import OPTIONS, OptionHandler
+from ota_signing_utils import AddSigningArgumentParse
 
 logger = logging.getLogger(__name__)
 
@@ -26,6 +31,8 @@
 OPTIONS.payload_signer_maximum_signature_size = None
 OPTIONS.package_key = None
 
+PAYLOAD_BIN = 'payload.bin'
+PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
 
 class SignerOptions(OptionHandler):
 
@@ -165,3 +172,52 @@
     cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file]
     common.RunAndCheckOutput(cmd)
     return out_file
+
+def GeneratePayloadProperties(payload_file):
+    properties_file = common.MakeTempFile(prefix="payload-properties-",
+                                          suffix=".txt")
+    cmd = ["delta_generator",
+           "--in_file=" + payload_file,
+           "--properties_file=" + properties_file]
+    common.RunAndCheckOutput(cmd)
+    return properties_file
+
+def SignOtaPackage(input_path, output_path):
+  payload_signer = PayloadSigner(
+      OPTIONS.package_key, OPTIONS.private_key_suffix,
+      None, OPTIONS.payload_signer, OPTIONS.payload_signer_args)
+  common.ZipExclude(input_path, output_path, [PAYLOAD_BIN, PAYLOAD_PROPERTIES_TXT])
+  with tempfile.NamedTemporaryFile() as unsigned_payload, zipfile.ZipFile(input_path, "r", allowZip64=True) as zfp:
+    with zfp.open("payload.bin") as payload_fp:
+      shutil.copyfileobj(payload_fp, unsigned_payload)
+    signed_payload = payload_signer.SignPayload(unsigned_payload.name)
+    properties_file = GeneratePayloadProperties(signed_payload)
+    with zipfile.ZipFile(output_path, "a", compression=zipfile.ZIP_STORED, allowZip64=True) as output_zfp:
+      common.ZipWrite(output_zfp, signed_payload, PAYLOAD_BIN)
+      common.ZipWrite(output_zfp, properties_file, PAYLOAD_PROPERTIES_TXT)
+
+
+def main(argv):
+  parser = argparse.ArgumentParser(
+      prog=argv[0], description="Given a series of .img files, produces a full OTA package that installs thoese images")
+  parser.add_argument("input_ota", type=str,
+                      help="Input OTA for signing")
+  parser.add_argument('output_ota', type=str,
+                      help='Output OTA for the signed package')
+  parser.add_argument("-v", action="store_true",
+                      help="Enable verbose logging", dest="verbose")
+  AddSigningArgumentParse(parser)
+  args = parser.parse_args(argv[1:])
+  input_ota = args.input_ota
+  output_ota = args.output_ota
+  if args.verbose:
+    OPTIONS.verbose = True
+  common.InitLogging()
+  if args.package_key:
+    OPTIONS.package_key = args.package_key
+  logger.info("Re-signing OTA package {}".format(input_ota))
+  SignOtaPackage(input_ota, output_ota)
+
+if __name__ == "__main__":
+  import sys
+  main(sys.argv)
\ No newline at end of file
diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py
index 2b45825..c595bb8 100755
--- a/tools/releasetools/sign_target_files_apks.py
+++ b/tools/releasetools/sign_target_files_apks.py
@@ -147,6 +147,34 @@
 
   --override_apex_keys <path>
       Replace all APEX keys with this private key
+
+  -k  (--package_key) <key>
+      Key to use to sign the package (default is the value of
+      default_system_dev_certificate from the input target-files's
+      META/misc_info.txt, or "build/make/target/product/security/testkey" if
+      that value is not specified).
+
+      For incremental OTAs, the default value is based on the source
+      target-file, not the target build.
+
+  --payload_signer <signer>
+      Specify the signer when signing the payload and metadata for A/B OTAs.
+      By default (i.e. without this flag), it calls 'openssl pkeyutl' to sign
+      with the package private key. If the private key cannot be accessed
+      directly, a payload signer that knows how to do that should be specified.
+      The signer will be supplied with "-inkey <path_to_key>",
+      "-in <input_file>" and "-out <output_file>" parameters.
+
+  --payload_signer_args <args>
+      Specify the arguments needed for payload signer.
+
+  --payload_signer_maximum_signature_size <signature_size>
+      The maximum signature size (in bytes) that would be generated by the given
+      payload signer. Only meaningful when custom payload signer is specified
+      via '--payload_signer'.
+      If the signer uses a RSA key, this should be the number of bytes to
+      represent the modulus. If it uses an EC key, this is the size of a
+      DER-encoded ECDSA signature.
 """
 
 from __future__ import print_function
@@ -162,7 +190,6 @@
 import re
 import shutil
 import stat
-import subprocess
 import sys
 import tempfile
 import zipfile
@@ -171,6 +198,8 @@
 import add_img_to_target_files
 import apex_utils
 import common
+import payload_signer
+from payload_signer import SignOtaPackage, PAYLOAD_BIN
 
 
 if sys.hexversion < 0x02070000:
@@ -241,6 +270,20 @@
   return filename.endswith(".apex") or filename.endswith(".capex")
 
 
+def IsOtaPackage(fp):
+  with zipfile.ZipFile(fp) as zfp:
+    if not PAYLOAD_BIN in zfp.namelist():
+      return False
+    with zfp.open(PAYLOAD_BIN, "r") as payload:
+      magic = payload.read(4)
+      return magic == b"CrAU"
+
+
+def IsEntryOtaPackage(input_zip, filename):
+  with input_zip.open(filename, "r") as fp:
+    return IsOtaPackage(fp)
+
+
 def GetApexFilename(filename):
   name = os.path.basename(filename)
   # Replace the suffix for compressed apex
@@ -515,6 +558,7 @@
   return data
 
 
+
 def IsBuildPropFile(filename):
   return filename in (
       "SYSTEM/etc/prop.default",
@@ -541,7 +585,7 @@
         filename.endswith("/prop.default")
 
 
-def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
+def ProcessTargetFiles(input_tf_zip: zipfile.ZipFile, output_tf_zip, misc_info,
                        apk_keys, apex_keys, key_passwords,
                        platform_api_level, codename_to_api_level_map,
                        compressed_extension):
@@ -631,6 +675,15 @@
             "        (skipped due to special cert string)" % (name,))
         common.ZipWriteStr(output_tf_zip, out_info, data)
 
+    elif filename.endswith(".zip") and IsEntryOtaPackage(input_tf_zip, filename):
+      logger.info("Re-signing OTA package {}".format(filename))
+      with tempfile.NamedTemporaryFile() as input_ota, tempfile.NamedTemporaryFile() as output_ota:
+        with input_tf_zip.open(filename, "r") as in_fp:
+          shutil.copyfileobj(in_fp, input_ota)
+          input_ota.flush()
+        SignOtaPackage(input_ota.name, output_ota.name)
+        common.ZipWrite(output_tf_zip, output_ota.name, filename,
+                        compress_type=zipfile.ZIP_STORED)
     # System properties.
     elif IsBuildPropFile(filename):
       print("Rewriting %s:" % (filename,))
@@ -1504,7 +1557,7 @@
           "override_apk_keys=",
           "override_apex_keys=",
       ],
-      extra_option_handler=option_handler)
+      extra_option_handler=[option_handler, payload_signer.signer_options])
 
   if len(args) != 2:
     common.Usage(__doc__)
@@ -1518,6 +1571,10 @@
                                allowZip64=True)
 
   misc_info = common.LoadInfoDict(input_zip)
+  if OPTIONS.package_key is None:
+      OPTIONS.package_key = misc_info.get(
+          "default_system_dev_certificate",
+          "build/make/target/product/security/testkey")
 
   BuildKeyMap(misc_info, key_mapping_options)