Home | History | Annotate | Download | only in releasetools
      1 #!/usr/bin/env python
      2 #
      3 # Copyright (C) 2008 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 """
     18 Signs all the APK files in a target-files zipfile, producing a new
     19 target-files zip.
     20 
     21 Usage:  sign_target_files_apks [flags] input_target_files output_target_files
     22 
     23   -e  (--extra_apks)  <name,name,...=key>
     24       Add extra APK/APEX name/key pairs as though they appeared in apkcerts.txt
     25       or apexkeys.txt (so mappings specified by -k and -d are applied). Keys
     26       specified in -e override any value for that app contained in the
     27       apkcerts.txt file, or the container key for an APEX. Option may be
     28       repeated to give multiple extra packages.
     29 
     30   --extra_apex_payload_key <name=key>
     31       Add a mapping for APEX package name to payload signing key, which will
     32       override the default payload signing key in apexkeys.txt. Note that the
     33       container key should be overridden via the `--extra_apks` flag above.
     34       Option may be repeated for multiple APEXes.
     35 
     36   --skip_apks_with_path_prefix  <prefix>
     37       Skip signing an APK if it has the matching prefix in its path. The prefix
     38       should be matching the entry name, which has partition names in upper
     39       case, e.g. "VENDOR/app/", or "SYSTEM_OTHER/preloads/". Option may be
     40       repeated to give multiple prefixes.
     41 
     42   -k  (--key_mapping)  <src_key=dest_key>
     43       Add a mapping from the key name as specified in apkcerts.txt (the
     44       src_key) to the real key you wish to sign the package with
     45       (dest_key).  Option may be repeated to give multiple key
     46       mappings.
     47 
     48   -d  (--default_key_mappings)  <dir>
     49       Set up the following key mappings:
     50 
     51         $devkey/devkey    ==>  $dir/releasekey
     52         $devkey/testkey   ==>  $dir/releasekey
     53         $devkey/media     ==>  $dir/media
     54         $devkey/shared    ==>  $dir/shared
     55         $devkey/platform  ==>  $dir/platform
     56 
     57       where $devkey is the directory part of the value of
     58       default_system_dev_certificate from the input target-files's
     59       META/misc_info.txt.  (Defaulting to "build/target/product/security"
     60       if the value is not present in misc_info.
     61 
     62       -d and -k options are added to the set of mappings in the order
     63       in which they appear on the command line.
     64 
     65   -o  (--replace_ota_keys)
     66       Replace the certificate (public key) used by OTA package verification
     67       with the ones specified in the input target_files zip (in the
     68       META/otakeys.txt file). Key remapping (-k and -d) is performed on the
     69       keys. For A/B devices, the payload verification key will be replaced
     70       as well. If there're multiple OTA keys, only the first one will be used
     71       for payload verification.
     72 
     73   -t  (--tag_changes)  <+tag>,<-tag>,...
     74       Comma-separated list of changes to make to the set of tags (in
     75       the last component of the build fingerprint).  Prefix each with
     76       '+' or '-' to indicate whether that tag should be added or
     77       removed.  Changes are processed in the order they appear.
     78       Default value is "-test-keys,-dev-keys,+release-keys".
     79 
     80   --replace_verity_private_key <key>
     81       Replace the private key used for verity signing. It expects a filename
     82       WITHOUT the extension (e.g. verity_key).
     83 
     84   --replace_verity_public_key <key>
     85       Replace the certificate (public key) used for verity verification. The
     86       key file replaces the one at BOOT/RAMDISK/verity_key (or ROOT/verity_key
     87       for devices using system_root_image). It expects the key filename WITH
     88       the extension (e.g. verity_key.pub).
     89 
     90   --replace_verity_keyid <path_to_X509_PEM_cert_file>
     91       Replace the veritykeyid in BOOT/cmdline of input_target_file_zip
     92       with keyid of the cert pointed by <path_to_X509_PEM_cert_file>.
     93 
     94   --avb_{boot,system,system_other,vendor,dtbo,vbmeta,vbmeta_system,
     95          vbmeta_vendor}_algorithm <algorithm>
     96   --avb_{boot,system,system_other,vendor,dtbo,vbmeta,vbmeta_system,
     97          vbmeta_vendor}_key <key>
     98       Use the specified algorithm (e.g. SHA256_RSA4096) and the key to AVB-sign
     99       the specified image. Otherwise it uses the existing values in info dict.
    100 
    101   --avb_{apex,boot,system,system_other,vendor,dtbo,vbmeta,vbmeta_system,
    102          vbmeta_vendor}_extra_args <args>
    103       Specify any additional args that are needed to AVB-sign the image
    104       (e.g. "--signing_helper /path/to/helper"). The args will be appended to
    105       the existing ones in info dict.
    106 """
    107 
    108 from __future__ import print_function
    109 
    110 import base64
    111 import copy
    112 import errno
    113 import gzip
    114 import itertools
    115 import logging
    116 import os
    117 import re
    118 import shutil
    119 import stat
    120 import subprocess
    121 import sys
    122 import tempfile
    123 import zipfile
    124 from xml.etree import ElementTree
    125 
    126 import add_img_to_target_files
    127 import apex_utils
    128 import common
    129 
    130 
    131 if sys.hexversion < 0x02070000:
    132   print("Python 2.7 or newer is required.", file=sys.stderr)
    133   sys.exit(1)
    134 
    135 
    136 logger = logging.getLogger(__name__)
    137 
    138 OPTIONS = common.OPTIONS
    139 
    140 OPTIONS.extra_apks = {}
    141 OPTIONS.extra_apex_payload_keys = {}
    142 OPTIONS.skip_apks_with_path_prefix = set()
    143 OPTIONS.key_map = {}
    144 OPTIONS.rebuild_recovery = False
    145 OPTIONS.replace_ota_keys = False
    146 OPTIONS.replace_verity_public_key = False
    147 OPTIONS.replace_verity_private_key = False
    148 OPTIONS.replace_verity_keyid = False
    149 OPTIONS.tag_changes = ("-test-keys", "-dev-keys", "+release-keys")
    150 OPTIONS.avb_keys = {}
    151 OPTIONS.avb_algorithms = {}
    152 OPTIONS.avb_extra_args = {}
    153 
    154 
    155 def GetApkCerts(certmap):
    156   # apply the key remapping to the contents of the file
    157   for apk, cert in certmap.iteritems():
    158     certmap[apk] = OPTIONS.key_map.get(cert, cert)
    159 
    160   # apply all the -e options, overriding anything in the file
    161   for apk, cert in OPTIONS.extra_apks.iteritems():
    162     if not cert:
    163       cert = "PRESIGNED"
    164     certmap[apk] = OPTIONS.key_map.get(cert, cert)
    165 
    166   return certmap
    167 
    168 
    169 def GetApexKeys(keys_info, key_map):
    170   """Gets APEX payload and container signing keys by applying the mapping rules.
    171 
    172   Presigned payload / container keys will be set accordingly.
    173 
    174   Args:
    175     keys_info: A dict that maps from APEX filenames to a tuple of (payload_key,
    176         container_key).
    177     key_map: A dict that overrides the keys, specified via command-line input.
    178 
    179   Returns:
    180     A dict that contains the updated APEX key mapping, which should be used for
    181     the current signing.
    182   """
    183   # Apply all the --extra_apex_payload_key options to override the payload
    184   # signing keys in the given keys_info.
    185   for apex, key in OPTIONS.extra_apex_payload_keys.items():
    186     if not key:
    187       key = 'PRESIGNED'
    188     if apex not in keys_info:
    189       logger.warning('Failed to find %s in target_files; Ignored', apex)
    190       continue
    191     keys_info[apex] = (key, keys_info[apex][1])
    192 
    193   # Apply the key remapping to container keys.
    194   for apex, (payload_key, container_key) in keys_info.items():
    195     keys_info[apex] = (payload_key, key_map.get(container_key, container_key))
    196 
    197   # Apply all the --extra_apks options to override the container keys.
    198   for apex, key in OPTIONS.extra_apks.items():
    199     # Skip non-APEX containers.
    200     if apex not in keys_info:
    201       continue
    202     if not key:
    203       key = 'PRESIGNED'
    204     keys_info[apex] = (keys_info[apex][0], key_map.get(key, key))
    205 
    206   return keys_info
    207 
    208 
    209 def GetApkFileInfo(filename, compressed_extension, skipped_prefixes):
    210   """Returns the APK info based on the given filename.
    211 
    212   Checks if the given filename (with path) looks like an APK file, by taking the
    213   compressed extension into consideration. If it appears to be an APK file,
    214   further checks if the APK file should be skipped when signing, based on the
    215   given path prefixes.
    216 
    217   Args:
    218     filename: Path to the file.
    219     compressed_extension: The extension string of compressed APKs (e.g. ".gz"),
    220         or None if there's no compressed APKs.
    221     skipped_prefixes: A set/list/tuple of the path prefixes to be skipped.
    222 
    223   Returns:
    224     (is_apk, is_compressed, should_be_skipped): is_apk indicates whether the
    225     given filename is an APK file. is_compressed indicates whether the APK file
    226     is compressed (only meaningful when is_apk is True). should_be_skipped
    227     indicates whether the filename matches any of the given prefixes to be
    228     skipped.
    229 
    230   Raises:
    231     AssertionError: On invalid compressed_extension or skipped_prefixes inputs.
    232   """
    233   assert compressed_extension is None or compressed_extension.startswith('.'), \
    234       "Invalid compressed_extension arg: '{}'".format(compressed_extension)
    235 
    236   # skipped_prefixes should be one of set/list/tuple types. Other types such as
    237   # str shouldn't be accepted.
    238   assert isinstance(skipped_prefixes, (set, list, tuple)), \
    239       "Invalid skipped_prefixes input type: {}".format(type(skipped_prefixes))
    240 
    241   compressed_apk_extension = (
    242       ".apk" + compressed_extension if compressed_extension else None)
    243   is_apk = (filename.endswith(".apk") or
    244             (compressed_apk_extension and
    245              filename.endswith(compressed_apk_extension)))
    246   if not is_apk:
    247     return (False, False, False)
    248 
    249   is_compressed = (compressed_apk_extension and
    250                    filename.endswith(compressed_apk_extension))
    251   should_be_skipped = filename.startswith(tuple(skipped_prefixes))
    252   return (True, is_compressed, should_be_skipped)
    253 
    254 
    255 def CheckApkAndApexKeysAvailable(input_tf_zip, known_keys,
    256                                  compressed_extension, apex_keys):
    257   """Checks that all the APKs and APEXes have keys specified.
    258 
    259   Args:
    260     input_tf_zip: An open target_files zip file.
    261     known_keys: A set of APKs and APEXes that have known signing keys.
    262     compressed_extension: The extension string of compressed APKs, such as
    263         '.gz', or None if there's no compressed APKs.
    264     apex_keys: A dict that contains the key mapping from APEX name to
    265         (payload_key, container_key).
    266 
    267   Raises:
    268     AssertionError: On finding unknown APKs and APEXes.
    269   """
    270   unknown_files = []
    271   for info in input_tf_zip.infolist():
    272     # Handle APEXes first, e.g. SYSTEM/apex/com.android.tzdata.apex.
    273     if (info.filename.startswith('SYSTEM/apex') and
    274         info.filename.endswith('.apex')):
    275       name = os.path.basename(info.filename)
    276       if name not in known_keys:
    277         unknown_files.append(name)
    278       continue
    279 
    280     # And APKs.
    281     (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo(
    282         info.filename, compressed_extension, OPTIONS.skip_apks_with_path_prefix)
    283     if not is_apk or should_be_skipped:
    284       continue
    285 
    286     name = os.path.basename(info.filename)
    287     if is_compressed:
    288       name = name[:-len(compressed_extension)]
    289     if name not in known_keys:
    290       unknown_files.append(name)
    291 
    292   assert not unknown_files, \
    293       ("No key specified for:\n  {}\n"
    294        "Use '-e <apkname>=' to specify a key (which may be an empty string to "
    295        "not sign this apk).".format("\n  ".join(unknown_files)))
    296 
    297   # For all the APEXes, double check that we won't have an APEX that has only
    298   # one of the payload / container keys set.
    299   if not apex_keys:
    300     return
    301 
    302   invalid_apexes = []
    303   for info in input_tf_zip.infolist():
    304     if (not info.filename.startswith('SYSTEM/apex') or
    305         not info.filename.endswith('.apex')):
    306       continue
    307 
    308     name = os.path.basename(info.filename)
    309     (payload_key, container_key) = apex_keys[name]
    310     if ((payload_key in common.SPECIAL_CERT_STRINGS and
    311          container_key not in common.SPECIAL_CERT_STRINGS) or
    312         (payload_key not in common.SPECIAL_CERT_STRINGS and
    313          container_key in common.SPECIAL_CERT_STRINGS)):
    314       invalid_apexes.append(
    315           "{}: payload_key {}, container_key {}".format(
    316               name, payload_key, container_key))
    317 
    318   assert not invalid_apexes, \
    319       "Invalid APEX keys specified:\n  {}\n".format(
    320           "\n  ".join(invalid_apexes))
    321 
    322 
    323 def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map,
    324             is_compressed):
    325   unsigned = tempfile.NamedTemporaryFile()
    326   unsigned.write(data)
    327   unsigned.flush()
    328 
    329   if is_compressed:
    330     uncompressed = tempfile.NamedTemporaryFile()
    331     with gzip.open(unsigned.name, "rb") as in_file, \
    332          open(uncompressed.name, "wb") as out_file:
    333       shutil.copyfileobj(in_file, out_file)
    334 
    335     # Finally, close the "unsigned" file (which is gzip compressed), and then
    336     # replace it with the uncompressed version.
    337     #
    338     # TODO(narayan): All this nastiness can be avoided if python 3.2 is in use,
    339     # we could just gzip / gunzip in-memory buffers instead.
    340     unsigned.close()
    341     unsigned = uncompressed
    342 
    343   signed = tempfile.NamedTemporaryFile()
    344 
    345   # For pre-N builds, don't upgrade to SHA-256 JAR signatures based on the APK's
    346   # minSdkVersion to avoid increasing incremental OTA update sizes. If an APK
    347   # didn't change, we don't want its signature to change due to the switch
    348   # from SHA-1 to SHA-256.
    349   # By default, APK signer chooses SHA-256 signatures if the APK's minSdkVersion
    350   # is 18 or higher. For pre-N builds we disable this mechanism by pretending
    351   # that the APK's minSdkVersion is 1.
    352   # For N+ builds, we let APK signer rely on the APK's minSdkVersion to
    353   # determine whether to use SHA-256.
    354   min_api_level = None
    355   if platform_api_level > 23:
    356     # Let APK signer choose whether to use SHA-1 or SHA-256, based on the APK's
    357     # minSdkVersion attribute
    358     min_api_level = None
    359   else:
    360     # Force APK signer to use SHA-1
    361     min_api_level = 1
    362 
    363   common.SignFile(unsigned.name, signed.name, keyname, pw,
    364                   min_api_level=min_api_level,
    365                   codename_to_api_level_map=codename_to_api_level_map)
    366 
    367   data = None
    368   if is_compressed:
    369     # Recompress the file after it has been signed.
    370     compressed = tempfile.NamedTemporaryFile()
    371     with open(signed.name, "rb") as in_file, \
    372          gzip.open(compressed.name, "wb") as out_file:
    373       shutil.copyfileobj(in_file, out_file)
    374 
    375     data = compressed.read()
    376     compressed.close()
    377   else:
    378     data = signed.read()
    379 
    380   unsigned.close()
    381   signed.close()
    382 
    383   return data
    384 
    385 
    386 def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
    387                        apk_keys, apex_keys, key_passwords,
    388                        platform_api_level, codename_to_api_level_map,
    389                        compressed_extension):
    390   # maxsize measures the maximum filename length, including the ones to be
    391   # skipped.
    392   maxsize = max(
    393       [len(os.path.basename(i.filename)) for i in input_tf_zip.infolist()
    394        if GetApkFileInfo(i.filename, compressed_extension, [])[0]])
    395   system_root_image = misc_info.get("system_root_image") == "true"
    396 
    397   for info in input_tf_zip.infolist():
    398     filename = info.filename
    399     if filename.startswith("IMAGES/"):
    400       continue
    401 
    402     # Skip split super images, which will be re-generated during signing.
    403     if filename.startswith("OTA/") and filename.endswith(".img"):
    404       continue
    405 
    406     data = input_tf_zip.read(filename)
    407     out_info = copy.copy(info)
    408     (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo(
    409         filename, compressed_extension, OPTIONS.skip_apks_with_path_prefix)
    410 
    411     if is_apk and should_be_skipped:
    412       # Copy skipped APKs verbatim.
    413       print(
    414           "NOT signing: %s\n"
    415           "        (skipped due to matching prefix)" % (filename,))
    416       common.ZipWriteStr(output_tf_zip, out_info, data)
    417 
    418     # Sign APKs.
    419     elif is_apk:
    420       name = os.path.basename(filename)
    421       if is_compressed:
    422         name = name[:-len(compressed_extension)]
    423 
    424       key = apk_keys[name]
    425       if key not in common.SPECIAL_CERT_STRINGS:
    426         print("    signing: %-*s (%s)" % (maxsize, name, key))
    427         signed_data = SignApk(data, key, key_passwords[key], platform_api_level,
    428                               codename_to_api_level_map, is_compressed)
    429         common.ZipWriteStr(output_tf_zip, out_info, signed_data)
    430       else:
    431         # an APK we're not supposed to sign.
    432         print(
    433             "NOT signing: %s\n"
    434             "        (skipped due to special cert string)" % (name,))
    435         common.ZipWriteStr(output_tf_zip, out_info, data)
    436 
    437     # Sign bundled APEX files.
    438     elif filename.startswith("SYSTEM/apex") and filename.endswith(".apex"):
    439       name = os.path.basename(filename)
    440       payload_key, container_key = apex_keys[name]
    441 
    442       # We've asserted not having a case with only one of them PRESIGNED.
    443       if (payload_key not in common.SPECIAL_CERT_STRINGS and
    444           container_key not in common.SPECIAL_CERT_STRINGS):
    445         print("    signing: %-*s container (%s)" % (
    446             maxsize, name, container_key))
    447         print("           : %-*s payload   (%s)" % (
    448             maxsize, name, payload_key))
    449 
    450         signed_apex = apex_utils.SignApex(
    451             data,
    452             payload_key,
    453             container_key,
    454             key_passwords[container_key],
    455             codename_to_api_level_map,
    456             OPTIONS.avb_extra_args.get('apex'))
    457         common.ZipWrite(output_tf_zip, signed_apex, filename)
    458 
    459       else:
    460         print(
    461             "NOT signing: %s\n"
    462             "        (skipped due to special cert string)" % (name,))
    463         common.ZipWriteStr(output_tf_zip, out_info, data)
    464 
    465     # AVB public keys for the installed APEXes, which will be updated later.
    466     elif (os.path.dirname(filename) == 'SYSTEM/etc/security/apex' and
    467           filename != 'SYSTEM/etc/security/apex/'):
    468       continue
    469 
    470     # System properties.
    471     elif filename in ("SYSTEM/build.prop",
    472                       "VENDOR/build.prop",
    473                       "SYSTEM/vendor/build.prop",
    474                       "ODM/build.prop",  # legacy
    475                       "ODM/etc/build.prop",
    476                       "VENDOR/odm/build.prop",  # legacy
    477                       "VENDOR/odm/etc/build.prop",
    478                       "PRODUCT/build.prop",
    479                       "SYSTEM/product/build.prop",
    480                       "PRODUCT_SERVICES/build.prop",
    481                       "SYSTEM/product_services/build.prop",
    482                       "SYSTEM/etc/prop.default",
    483                       "BOOT/RAMDISK/prop.default",
    484                       "BOOT/RAMDISK/default.prop",  # legacy
    485                       "ROOT/default.prop",  # legacy
    486                       "RECOVERY/RAMDISK/prop.default",
    487                       "RECOVERY/RAMDISK/default.prop"):  # legacy
    488       print("Rewriting %s:" % (filename,))
    489       if stat.S_ISLNK(info.external_attr >> 16):
    490         new_data = data
    491       else:
    492         new_data = RewriteProps(data)
    493       common.ZipWriteStr(output_tf_zip, out_info, new_data)
    494 
    495     # Replace the certs in *mac_permissions.xml (there could be multiple, such
    496     # as {system,vendor}/etc/selinux/{plat,nonplat}_mac_permissions.xml).
    497     elif filename.endswith("mac_permissions.xml"):
    498       print("Rewriting %s with new keys." % (filename,))
    499       new_data = ReplaceCerts(data)
    500       common.ZipWriteStr(output_tf_zip, out_info, new_data)
    501 
    502     # Ask add_img_to_target_files to rebuild the recovery patch if needed.
    503     elif filename in ("SYSTEM/recovery-from-boot.p",
    504                       "SYSTEM/etc/recovery.img",
    505                       "SYSTEM/bin/install-recovery.sh"):
    506       OPTIONS.rebuild_recovery = True
    507 
    508     # Don't copy OTA certs if we're replacing them.
    509     elif (
    510         OPTIONS.replace_ota_keys and
    511         filename in (
    512             "BOOT/RAMDISK/system/etc/security/otacerts.zip",
    513             "BOOT/RAMDISK/system/etc/update_engine/update-payload-key.pub.pem",
    514             "RECOVERY/RAMDISK/system/etc/security/otacerts.zip",
    515             "SYSTEM/etc/security/otacerts.zip",
    516             "SYSTEM/etc/update_engine/update-payload-key.pub.pem")):
    517       pass
    518 
    519     # Skip META/misc_info.txt since we will write back the new values later.
    520     elif filename == "META/misc_info.txt":
    521       pass
    522 
    523     # Skip verity public key if we will replace it.
    524     elif (OPTIONS.replace_verity_public_key and
    525           filename in ("BOOT/RAMDISK/verity_key",
    526                        "ROOT/verity_key")):
    527       pass
    528 
    529     # Skip verity keyid (for system_root_image use) if we will replace it.
    530     elif OPTIONS.replace_verity_keyid and filename == "BOOT/cmdline":
    531       pass
    532 
    533     # Skip the care_map as we will regenerate the system/vendor images.
    534     elif filename == "META/care_map.pb" or filename == "META/care_map.txt":
    535       pass
    536 
    537     # Updates system_other.avbpubkey in /product/etc/.
    538     elif filename in (
    539         "PRODUCT/etc/security/avb/system_other.avbpubkey",
    540         "SYSTEM/product/etc/security/avb/system_other.avbpubkey"):
    541       # Only update system_other's public key, if the corresponding signing
    542       # key is specified via --avb_system_other_key.
    543       signing_key = OPTIONS.avb_keys.get("system_other")
    544       if signing_key:
    545         public_key = common.ExtractAvbPublicKey(signing_key)
    546         print("    Rewriting AVB public key of system_other in /product")
    547         common.ZipWrite(output_tf_zip, public_key, filename)
    548 
    549     # Should NOT sign boot-debug.img.
    550     elif filename in (
    551         "BOOT/RAMDISK/force_debuggable",
    552         "RECOVERY/RAMDISK/force_debuggable"
    553         "RECOVERY/RAMDISK/first_stage_ramdisk/force_debuggable"):
    554       raise common.ExternalError("debuggable boot.img cannot be signed")
    555 
    556     # A non-APK file; copy it verbatim.
    557     else:
    558       common.ZipWriteStr(output_tf_zip, out_info, data)
    559 
    560   if OPTIONS.replace_ota_keys:
    561     ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info)
    562 
    563   # Replace the keyid string in misc_info dict.
    564   if OPTIONS.replace_verity_private_key:
    565     ReplaceVerityPrivateKey(misc_info, OPTIONS.replace_verity_private_key[1])
    566 
    567   if OPTIONS.replace_verity_public_key:
    568     dest = "ROOT/verity_key" if system_root_image else "BOOT/RAMDISK/verity_key"
    569     # We are replacing the one in boot image only, since the one under
    570     # recovery won't ever be needed.
    571     ReplaceVerityPublicKey(
    572         output_tf_zip, dest, OPTIONS.replace_verity_public_key[1])
    573 
    574   # Replace the keyid string in BOOT/cmdline.
    575   if OPTIONS.replace_verity_keyid:
    576     ReplaceVerityKeyId(input_tf_zip, output_tf_zip,
    577                        OPTIONS.replace_verity_keyid[1])
    578 
    579   # Replace the AVB signing keys, if any.
    580   ReplaceAvbSigningKeys(misc_info)
    581 
    582   # Write back misc_info with the latest values.
    583   ReplaceMiscInfoTxt(input_tf_zip, output_tf_zip, misc_info)
    584 
    585 
    586 def ReplaceCerts(data):
    587   """Replaces all the occurences of X.509 certs with the new ones.
    588 
    589   The mapping info is read from OPTIONS.key_map. Non-existent certificate will
    590   be skipped. After the replacement, it additionally checks for duplicate
    591   entries, which would otherwise fail the policy loading code in
    592   frameworks/base/services/core/java/com/android/server/pm/SELinuxMMAC.java.
    593 
    594   Args:
    595     data: Input string that contains a set of X.509 certs.
    596 
    597   Returns:
    598     A string after the replacement.
    599 
    600   Raises:
    601     AssertionError: On finding duplicate entries.
    602   """
    603   for old, new in OPTIONS.key_map.iteritems():
    604     if OPTIONS.verbose:
    605       print("    Replacing %s.x509.pem with %s.x509.pem" % (old, new))
    606 
    607     try:
    608       with open(old + ".x509.pem") as old_fp:
    609         old_cert16 = base64.b16encode(
    610             common.ParseCertificate(old_fp.read())).lower()
    611       with open(new + ".x509.pem") as new_fp:
    612         new_cert16 = base64.b16encode(
    613             common.ParseCertificate(new_fp.read())).lower()
    614     except IOError as e:
    615       if OPTIONS.verbose or e.errno != errno.ENOENT:
    616         print("    Error accessing %s: %s.\nSkip replacing %s.x509.pem with "
    617               "%s.x509.pem." % (e.filename, e.strerror, old, new))
    618       continue
    619 
    620     # Only match entire certs.
    621     pattern = "\\b" + old_cert16 + "\\b"
    622     (data, num) = re.subn(pattern, new_cert16, data, flags=re.IGNORECASE)
    623 
    624     if OPTIONS.verbose:
    625       print("    Replaced %d occurence(s) of %s.x509.pem with %s.x509.pem" % (
    626           num, old, new))
    627 
    628   # Verify that there're no duplicate entries after the replacement. Note that
    629   # it's only checking entries with global seinfo at the moment (i.e. ignoring
    630   # the ones with inner packages). (Bug: 69479366)
    631   root = ElementTree.fromstring(data)
    632   signatures = [signer.attrib['signature'] for signer in root.findall('signer')]
    633   assert len(signatures) == len(set(signatures)), \
    634       "Found duplicate entries after cert replacement: {}".format(data)
    635 
    636   return data
    637 
    638 
    639 def EditTags(tags):
    640   """Applies the edits to the tag string as specified in OPTIONS.tag_changes.
    641 
    642   Args:
    643     tags: The input string that contains comma-separated tags.
    644 
    645   Returns:
    646     The updated tags (comma-separated and sorted).
    647   """
    648   tags = set(tags.split(","))
    649   for ch in OPTIONS.tag_changes:
    650     if ch[0] == "-":
    651       tags.discard(ch[1:])
    652     elif ch[0] == "+":
    653       tags.add(ch[1:])
    654   return ",".join(sorted(tags))
    655 
    656 
    657 def RewriteProps(data):
    658   """Rewrites the system properties in the given string.
    659 
    660   Each property is expected in 'key=value' format. The properties that contain
    661   build tags (i.e. test-keys, dev-keys) will be updated accordingly by calling
    662   EditTags().
    663 
    664   Args:
    665     data: Input string, separated by newlines.
    666 
    667   Returns:
    668     The string with modified properties.
    669   """
    670   output = []
    671   for line in data.split("\n"):
    672     line = line.strip()
    673     original_line = line
    674     if line and line[0] != '#' and "=" in line:
    675       key, value = line.split("=", 1)
    676       if (key.startswith("ro.") and
    677           key.endswith((".build.fingerprint", ".build.thumbprint"))):
    678         pieces = value.split("/")
    679         pieces[-1] = EditTags(pieces[-1])
    680         value = "/".join(pieces)
    681       elif key == "ro.bootimage.build.fingerprint":
    682         pieces = value.split("/")
    683         pieces[-1] = EditTags(pieces[-1])
    684         value = "/".join(pieces)
    685       elif key == "ro.build.description":
    686         pieces = value.split(" ")
    687         assert len(pieces) == 5
    688         pieces[-1] = EditTags(pieces[-1])
    689         value = " ".join(pieces)
    690       elif key.startswith("ro.") and key.endswith(".build.tags"):
    691         value = EditTags(value)
    692       elif key == "ro.build.display.id":
    693         # change, eg, "JWR66N dev-keys" to "JWR66N"
    694         value = value.split()
    695         if len(value) > 1 and value[-1].endswith("-keys"):
    696           value.pop()
    697         value = " ".join(value)
    698       line = key + "=" + value
    699     if line != original_line:
    700       print("  replace: ", original_line)
    701       print("     with: ", line)
    702     output.append(line)
    703   return "\n".join(output) + "\n"
    704 
    705 
    706 def WriteOtacerts(output_zip, filename, keys):
    707   """Constructs a zipfile from given keys; and writes it to output_zip.
    708 
    709   Args:
    710     output_zip: The output target_files zip.
    711     filename: The archive name in the output zip.
    712     keys: A list of public keys to use during OTA package verification.
    713   """
    714 
    715   try:
    716     from StringIO import StringIO
    717   except ImportError:
    718     from io import StringIO
    719   temp_file = StringIO()
    720   certs_zip = zipfile.ZipFile(temp_file, "w")
    721   for k in keys:
    722     common.ZipWrite(certs_zip, k)
    723   common.ZipClose(certs_zip)
    724   common.ZipWriteStr(output_zip, filename, temp_file.getvalue())
    725 
    726 
    727 def ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info):
    728   try:
    729     keylist = input_tf_zip.read("META/otakeys.txt").split()
    730   except KeyError:
    731     raise common.ExternalError("can't read META/otakeys.txt from input")
    732 
    733   extra_recovery_keys = misc_info.get("extra_recovery_keys")
    734   if extra_recovery_keys:
    735     extra_recovery_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem"
    736                            for k in extra_recovery_keys.split()]
    737     if extra_recovery_keys:
    738       print("extra recovery-only key(s): " + ", ".join(extra_recovery_keys))
    739   else:
    740     extra_recovery_keys = []
    741 
    742   mapped_keys = []
    743   for k in keylist:
    744     m = re.match(r"^(.*)\.x509\.pem$", k)
    745     if not m:
    746       raise common.ExternalError(
    747           "can't parse \"%s\" from META/otakeys.txt" % (k,))
    748     k = m.group(1)
    749     mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem")
    750 
    751   if mapped_keys:
    752     print("using:\n   ", "\n   ".join(mapped_keys))
    753     print("for OTA package verification")
    754   else:
    755     devkey = misc_info.get("default_system_dev_certificate",
    756                            "build/target/product/security/testkey")
    757     mapped_devkey = OPTIONS.key_map.get(devkey, devkey)
    758     if mapped_devkey != devkey:
    759       misc_info["default_system_dev_certificate"] = mapped_devkey
    760     mapped_keys.append(mapped_devkey + ".x509.pem")
    761     print("META/otakeys.txt has no keys; using %s for OTA package"
    762           " verification." % (mapped_keys[0],))
    763 
    764   # recovery now uses the same x509.pem version of the keys.
    765   # extra_recovery_keys are used only in recovery.
    766   if misc_info.get("recovery_as_boot") == "true":
    767     recovery_keys_location = "BOOT/RAMDISK/system/etc/security/otacerts.zip"
    768   else:
    769     recovery_keys_location = "RECOVERY/RAMDISK/system/etc/security/otacerts.zip"
    770 
    771   WriteOtacerts(output_tf_zip, recovery_keys_location,
    772                 mapped_keys + extra_recovery_keys)
    773 
    774   # SystemUpdateActivity uses the x509.pem version of the keys, but
    775   # put into a zipfile system/etc/security/otacerts.zip.
    776   # We DO NOT include the extra_recovery_keys (if any) here.
    777   WriteOtacerts(output_tf_zip, "SYSTEM/etc/security/otacerts.zip", mapped_keys)
    778 
    779   # For A/B devices, update the payload verification key.
    780   if misc_info.get("ab_update") == "true":
    781     # Unlike otacerts.zip that may contain multiple keys, we can only specify
    782     # ONE payload verification key.
    783     if len(mapped_keys) > 1:
    784       print("\n  WARNING: Found more than one OTA keys; Using the first one"
    785             " as payload verification key.\n\n")
    786 
    787     print("Using %s for payload verification." % (mapped_keys[0],))
    788     pubkey = common.ExtractPublicKey(mapped_keys[0])
    789     common.ZipWriteStr(
    790         output_tf_zip,
    791         "SYSTEM/etc/update_engine/update-payload-key.pub.pem",
    792         pubkey)
    793     common.ZipWriteStr(
    794         output_tf_zip,
    795         "BOOT/RAMDISK/system/etc/update_engine/update-payload-key.pub.pem",
    796         pubkey)
    797 
    798 
    799 def ReplaceVerityPublicKey(output_zip, filename, key_path):
    800   """Replaces the verity public key at the given path in the given zip.
    801 
    802   Args:
    803     output_zip: The output target_files zip.
    804     filename: The archive name in the output zip.
    805     key_path: The path to the public key.
    806   """
    807   print("Replacing verity public key with %s" % (key_path,))
    808   common.ZipWrite(output_zip, key_path, arcname=filename)
    809 
    810 
    811 def ReplaceVerityPrivateKey(misc_info, key_path):
    812   """Replaces the verity private key in misc_info dict.
    813 
    814   Args:
    815     misc_info: The info dict.
    816     key_path: The path to the private key in PKCS#8 format.
    817   """
    818   print("Replacing verity private key with %s" % (key_path,))
    819   misc_info["verity_key"] = key_path
    820 
    821 
    822 def ReplaceVerityKeyId(input_zip, output_zip, key_path):
    823   """Replaces the veritykeyid parameter in BOOT/cmdline.
    824 
    825   Args:
    826     input_zip: The input target_files zip, which should be already open.
    827     output_zip: The output target_files zip, which should be already open and
    828         writable.
    829     key_path: The path to the PEM encoded X.509 certificate.
    830   """
    831   in_cmdline = input_zip.read("BOOT/cmdline")
    832   # Copy in_cmdline to output_zip if veritykeyid is not present.
    833   if "veritykeyid" not in in_cmdline:
    834     common.ZipWriteStr(output_zip, "BOOT/cmdline", in_cmdline)
    835     return
    836 
    837   out_buffer = []
    838   for param in in_cmdline.split():
    839     if "veritykeyid" not in param:
    840       out_buffer.append(param)
    841       continue
    842 
    843     # Extract keyid using openssl command.
    844     p = common.Run(["openssl", "x509", "-in", key_path, "-text"],
    845                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    846     keyid, stderr = p.communicate()
    847     assert p.returncode == 0, "Failed to dump certificate: {}".format(stderr)
    848     keyid = re.search(
    849         r'keyid:([0-9a-fA-F:]*)', keyid).group(1).replace(':', '').lower()
    850     print("Replacing verity keyid with {}".format(keyid))
    851     out_buffer.append("veritykeyid=id:%s" % (keyid,))
    852 
    853   out_cmdline = ' '.join(out_buffer).strip() + '\n'
    854   common.ZipWriteStr(output_zip, "BOOT/cmdline", out_cmdline)
    855 
    856 
    857 def ReplaceMiscInfoTxt(input_zip, output_zip, misc_info):
    858   """Replaces META/misc_info.txt.
    859 
    860   Only writes back the ones in the original META/misc_info.txt. Because the
    861   current in-memory dict contains additional items computed at runtime.
    862   """
    863   misc_info_old = common.LoadDictionaryFromLines(
    864       input_zip.read('META/misc_info.txt').split('\n'))
    865   items = []
    866   for key in sorted(misc_info):
    867     if key in misc_info_old:
    868       items.append('%s=%s' % (key, misc_info[key]))
    869   common.ZipWriteStr(output_zip, "META/misc_info.txt", '\n'.join(items))
    870 
    871 
    872 def ReplaceAvbSigningKeys(misc_info):
    873   """Replaces the AVB signing keys."""
    874 
    875   AVB_FOOTER_ARGS_BY_PARTITION = {
    876       'boot' : 'avb_boot_add_hash_footer_args',
    877       'dtbo' : 'avb_dtbo_add_hash_footer_args',
    878       'recovery' : 'avb_recovery_add_hash_footer_args',
    879       'system' : 'avb_system_add_hashtree_footer_args',
    880       'system_other' : 'avb_system_other_add_hashtree_footer_args',
    881       'vendor' : 'avb_vendor_add_hashtree_footer_args',
    882       'vbmeta' : 'avb_vbmeta_args',
    883       'vbmeta_system' : 'avb_vbmeta_system_args',
    884       'vbmeta_vendor' : 'avb_vbmeta_vendor_args',
    885   }
    886 
    887   def ReplaceAvbPartitionSigningKey(partition):
    888     key = OPTIONS.avb_keys.get(partition)
    889     if not key:
    890       return
    891 
    892     algorithm = OPTIONS.avb_algorithms.get(partition)
    893     assert algorithm, 'Missing AVB signing algorithm for %s' % (partition,)
    894 
    895     print('Replacing AVB signing key for %s with "%s" (%s)' % (
    896         partition, key, algorithm))
    897     misc_info['avb_' + partition + '_algorithm'] = algorithm
    898     misc_info['avb_' + partition + '_key_path'] = key
    899 
    900     extra_args = OPTIONS.avb_extra_args.get(partition)
    901     if extra_args:
    902       print('Setting extra AVB signing args for %s to "%s"' % (
    903           partition, extra_args))
    904       args_key = AVB_FOOTER_ARGS_BY_PARTITION[partition]
    905       misc_info[args_key] = (misc_info.get(args_key, '') + ' ' + extra_args)
    906 
    907   for partition in AVB_FOOTER_ARGS_BY_PARTITION:
    908     ReplaceAvbPartitionSigningKey(partition)
    909 
    910 
    911 def BuildKeyMap(misc_info, key_mapping_options):
    912   for s, d in key_mapping_options:
    913     if s is None:   # -d option
    914       devkey = misc_info.get("default_system_dev_certificate",
    915                              "build/target/product/security/testkey")
    916       devkeydir = os.path.dirname(devkey)
    917 
    918       OPTIONS.key_map.update({
    919           devkeydir + "/testkey":  d + "/releasekey",
    920           devkeydir + "/devkey":   d + "/releasekey",
    921           devkeydir + "/media":    d + "/media",
    922           devkeydir + "/shared":   d + "/shared",
    923           devkeydir + "/platform": d + "/platform",
    924           })
    925     else:
    926       OPTIONS.key_map[s] = d
    927 
    928 
    929 def GetApiLevelAndCodename(input_tf_zip):
    930   data = input_tf_zip.read("SYSTEM/build.prop")
    931   api_level = None
    932   codename = None
    933   for line in data.split("\n"):
    934     line = line.strip()
    935     if line and line[0] != '#' and "=" in line:
    936       key, value = line.split("=", 1)
    937       key = key.strip()
    938       if key == "ro.build.version.sdk":
    939         api_level = int(value.strip())
    940       elif key == "ro.build.version.codename":
    941         codename = value.strip()
    942 
    943   if api_level is None:
    944     raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
    945   if codename is None:
    946     raise ValueError("No ro.build.version.codename in SYSTEM/build.prop")
    947 
    948   return (api_level, codename)
    949 
    950 
    951 def GetCodenameToApiLevelMap(input_tf_zip):
    952   data = input_tf_zip.read("SYSTEM/build.prop")
    953   api_level = None
    954   codenames = None
    955   for line in data.split("\n"):
    956     line = line.strip()
    957     if line and line[0] != '#' and "=" in line:
    958       key, value = line.split("=", 1)
    959       key = key.strip()
    960       if key == "ro.build.version.sdk":
    961         api_level = int(value.strip())
    962       elif key == "ro.build.version.all_codenames":
    963         codenames = value.strip().split(",")
    964 
    965   if api_level is None:
    966     raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
    967   if codenames is None:
    968     raise ValueError("No ro.build.version.all_codenames in SYSTEM/build.prop")
    969 
    970   result = dict()
    971   for codename in codenames:
    972     codename = codename.strip()
    973     if codename:
    974       result[codename] = api_level
    975   return result
    976 
    977 
    978 def ReadApexKeysInfo(tf_zip):
    979   """Parses the APEX keys info from a given target-files zip.
    980 
    981   Given a target-files ZipFile, parses the META/apexkeys.txt entry and returns a
    982   dict that contains the mapping from APEX names (e.g. com.android.tzdata) to a
    983   tuple of (payload_key, container_key).
    984 
    985   Args:
    986     tf_zip: The input target_files ZipFile (already open).
    987 
    988   Returns:
    989     (payload_key, container_key): payload_key contains the path to the payload
    990         signing key; container_key contains the path to the container signing
    991         key.
    992   """
    993   keys = {}
    994   for line in tf_zip.read("META/apexkeys.txt").split("\n"):
    995     line = line.strip()
    996     if not line:
    997       continue
    998     matches = re.match(
    999         r'^name="(?P<NAME>.*)"\s+'
   1000         r'public_key="(?P<PAYLOAD_PUBLIC_KEY>.*)"\s+'
   1001         r'private_key="(?P<PAYLOAD_PRIVATE_KEY>.*)"\s+'
   1002         r'container_certificate="(?P<CONTAINER_CERT>.*)"\s+'
   1003         r'container_private_key="(?P<CONTAINER_PRIVATE_KEY>.*)"$',
   1004         line)
   1005     if not matches:
   1006       continue
   1007 
   1008     name = matches.group('NAME')
   1009     payload_private_key = matches.group("PAYLOAD_PRIVATE_KEY")
   1010 
   1011     def CompareKeys(pubkey, pubkey_suffix, privkey, privkey_suffix):
   1012       pubkey_suffix_len = len(pubkey_suffix)
   1013       privkey_suffix_len = len(privkey_suffix)
   1014       return (pubkey.endswith(pubkey_suffix) and
   1015               privkey.endswith(privkey_suffix) and
   1016               pubkey[:-pubkey_suffix_len] == privkey[:-privkey_suffix_len])
   1017 
   1018     # Sanity check on the container key names, as we'll carry them without the
   1019     # extensions. This doesn't apply to payload keys though, which we will use
   1020     # full names only.
   1021     container_cert = matches.group("CONTAINER_CERT")
   1022     container_private_key = matches.group("CONTAINER_PRIVATE_KEY")
   1023     if container_cert == 'PRESIGNED' and container_private_key == 'PRESIGNED':
   1024       container_key = 'PRESIGNED'
   1025     elif CompareKeys(
   1026         container_cert, OPTIONS.public_key_suffix,
   1027         container_private_key, OPTIONS.private_key_suffix):
   1028       container_key = container_cert[:-len(OPTIONS.public_key_suffix)]
   1029     else:
   1030       raise ValueError("Failed to parse container keys: \n{}".format(line))
   1031 
   1032     keys[name] = (payload_private_key, container_key)
   1033 
   1034   return keys
   1035 
   1036 
   1037 def main(argv):
   1038 
   1039   key_mapping_options = []
   1040 
   1041   def option_handler(o, a):
   1042     if o in ("-e", "--extra_apks"):
   1043       names, key = a.split("=")
   1044       names = names.split(",")
   1045       for n in names:
   1046         OPTIONS.extra_apks[n] = key
   1047     elif o == "--extra_apex_payload_key":
   1048       apex_name, key = a.split("=")
   1049       OPTIONS.extra_apex_payload_keys[apex_name] = key
   1050     elif o == "--skip_apks_with_path_prefix":
   1051       # Sanity check the prefix, which must be in all upper case.
   1052       prefix = a.split('/')[0]
   1053       if not prefix or prefix != prefix.upper():
   1054         raise ValueError("Invalid path prefix '%s'" % (a,))
   1055       OPTIONS.skip_apks_with_path_prefix.add(a)
   1056     elif o in ("-d", "--default_key_mappings"):
   1057       key_mapping_options.append((None, a))
   1058     elif o in ("-k", "--key_mapping"):
   1059       key_mapping_options.append(a.split("=", 1))
   1060     elif o in ("-o", "--replace_ota_keys"):
   1061       OPTIONS.replace_ota_keys = True
   1062     elif o in ("-t", "--tag_changes"):
   1063       new = []
   1064       for i in a.split(","):
   1065         i = i.strip()
   1066         if not i or i[0] not in "-+":
   1067           raise ValueError("Bad tag change '%s'" % (i,))
   1068         new.append(i[0] + i[1:].strip())
   1069       OPTIONS.tag_changes = tuple(new)
   1070     elif o == "--replace_verity_public_key":
   1071       OPTIONS.replace_verity_public_key = (True, a)
   1072     elif o == "--replace_verity_private_key":
   1073       OPTIONS.replace_verity_private_key = (True, a)
   1074     elif o == "--replace_verity_keyid":
   1075       OPTIONS.replace_verity_keyid = (True, a)
   1076     elif o == "--avb_vbmeta_key":
   1077       OPTIONS.avb_keys['vbmeta'] = a
   1078     elif o == "--avb_vbmeta_algorithm":
   1079       OPTIONS.avb_algorithms['vbmeta'] = a
   1080     elif o == "--avb_vbmeta_extra_args":
   1081       OPTIONS.avb_extra_args['vbmeta'] = a
   1082     elif o == "--avb_boot_key":
   1083       OPTIONS.avb_keys['boot'] = a
   1084     elif o == "--avb_boot_algorithm":
   1085       OPTIONS.avb_algorithms['boot'] = a
   1086     elif o == "--avb_boot_extra_args":
   1087       OPTIONS.avb_extra_args['boot'] = a
   1088     elif o == "--avb_dtbo_key":
   1089       OPTIONS.avb_keys['dtbo'] = a
   1090     elif o == "--avb_dtbo_algorithm":
   1091       OPTIONS.avb_algorithms['dtbo'] = a
   1092     elif o == "--avb_dtbo_extra_args":
   1093       OPTIONS.avb_extra_args['dtbo'] = a
   1094     elif o == "--avb_system_key":
   1095       OPTIONS.avb_keys['system'] = a
   1096     elif o == "--avb_system_algorithm":
   1097       OPTIONS.avb_algorithms['system'] = a
   1098     elif o == "--avb_system_extra_args":
   1099       OPTIONS.avb_extra_args['system'] = a
   1100     elif o == "--avb_system_other_key":
   1101       OPTIONS.avb_keys['system_other'] = a
   1102     elif o == "--avb_system_other_algorithm":
   1103       OPTIONS.avb_algorithms['system_other'] = a
   1104     elif o == "--avb_system_other_extra_args":
   1105       OPTIONS.avb_extra_args['system_other'] = a
   1106     elif o == "--avb_vendor_key":
   1107       OPTIONS.avb_keys['vendor'] = a
   1108     elif o == "--avb_vendor_algorithm":
   1109       OPTIONS.avb_algorithms['vendor'] = a
   1110     elif o == "--avb_vendor_extra_args":
   1111       OPTIONS.avb_extra_args['vendor'] = a
   1112     elif o == "--avb_vbmeta_system_key":
   1113       OPTIONS.avb_keys['vbmeta_system'] = a
   1114     elif o == "--avb_vbmeta_system_algorithm":
   1115       OPTIONS.avb_algorithms['vbmeta_system'] = a
   1116     elif o == "--avb_vbmeta_system_extra_args":
   1117       OPTIONS.avb_extra_args['vbmeta_system'] = a
   1118     elif o == "--avb_vbmeta_vendor_key":
   1119       OPTIONS.avb_keys['vbmeta_vendor'] = a
   1120     elif o == "--avb_vbmeta_vendor_algorithm":
   1121       OPTIONS.avb_algorithms['vbmeta_vendor'] = a
   1122     elif o == "--avb_vbmeta_vendor_extra_args":
   1123       OPTIONS.avb_extra_args['vbmeta_vendor'] = a
   1124     elif o == "--avb_apex_extra_args":
   1125       OPTIONS.avb_extra_args['apex'] = a
   1126     else:
   1127       return False
   1128     return True
   1129 
   1130   args = common.ParseOptions(
   1131       argv, __doc__,
   1132       extra_opts="e:d:k:ot:",
   1133       extra_long_opts=[
   1134           "extra_apks=",
   1135           "extra_apex_payload_key=",
   1136           "skip_apks_with_path_prefix=",
   1137           "default_key_mappings=",
   1138           "key_mapping=",
   1139           "replace_ota_keys",
   1140           "tag_changes=",
   1141           "replace_verity_public_key=",
   1142           "replace_verity_private_key=",
   1143           "replace_verity_keyid=",
   1144           "avb_apex_extra_args=",
   1145           "avb_vbmeta_algorithm=",
   1146           "avb_vbmeta_key=",
   1147           "avb_vbmeta_extra_args=",
   1148           "avb_boot_algorithm=",
   1149           "avb_boot_key=",
   1150           "avb_boot_extra_args=",
   1151           "avb_dtbo_algorithm=",
   1152           "avb_dtbo_key=",
   1153           "avb_dtbo_extra_args=",
   1154           "avb_system_algorithm=",
   1155           "avb_system_key=",
   1156           "avb_system_extra_args=",
   1157           "avb_system_other_algorithm=",
   1158           "avb_system_other_key=",
   1159           "avb_system_other_extra_args=",
   1160           "avb_vendor_algorithm=",
   1161           "avb_vendor_key=",
   1162           "avb_vendor_extra_args=",
   1163           "avb_vbmeta_system_algorithm=",
   1164           "avb_vbmeta_system_key=",
   1165           "avb_vbmeta_system_extra_args=",
   1166           "avb_vbmeta_vendor_algorithm=",
   1167           "avb_vbmeta_vendor_key=",
   1168           "avb_vbmeta_vendor_extra_args=",
   1169       ],
   1170       extra_option_handler=option_handler)
   1171 
   1172   if len(args) != 2:
   1173     common.Usage(__doc__)
   1174     sys.exit(1)
   1175 
   1176   common.InitLogging()
   1177 
   1178   input_zip = zipfile.ZipFile(args[0], "r")
   1179   output_zip = zipfile.ZipFile(args[1], "w",
   1180                                compression=zipfile.ZIP_DEFLATED,
   1181                                allowZip64=True)
   1182 
   1183   misc_info = common.LoadInfoDict(input_zip)
   1184 
   1185   BuildKeyMap(misc_info, key_mapping_options)
   1186 
   1187   apk_keys_info, compressed_extension = common.ReadApkCerts(input_zip)
   1188   apk_keys = GetApkCerts(apk_keys_info)
   1189 
   1190   apex_keys_info = ReadApexKeysInfo(input_zip)
   1191   apex_keys = GetApexKeys(apex_keys_info, apk_keys)
   1192 
   1193   CheckApkAndApexKeysAvailable(
   1194       input_zip,
   1195       set(apk_keys.keys()) | set(apex_keys.keys()),
   1196       compressed_extension,
   1197       apex_keys)
   1198 
   1199   key_passwords = common.GetKeyPasswords(
   1200       set(apk_keys.values()) | set(itertools.chain(*apex_keys.values())))
   1201   platform_api_level, _ = GetApiLevelAndCodename(input_zip)
   1202   codename_to_api_level_map = GetCodenameToApiLevelMap(input_zip)
   1203 
   1204   ProcessTargetFiles(input_zip, output_zip, misc_info,
   1205                      apk_keys, apex_keys, key_passwords,
   1206                      platform_api_level, codename_to_api_level_map,
   1207                      compressed_extension)
   1208 
   1209   common.ZipClose(input_zip)
   1210   common.ZipClose(output_zip)
   1211 
   1212   # Skip building userdata.img and cache.img when signing the target files.
   1213   new_args = ["--is_signing"]
   1214   # add_img_to_target_files builds the system image from scratch, so the
   1215   # recovery patch is guaranteed to be regenerated there.
   1216   if OPTIONS.rebuild_recovery:
   1217     new_args.append("--rebuild_recovery")
   1218   new_args.append(args[1])
   1219   add_img_to_target_files.main(new_args)
   1220 
   1221   print("done.")
   1222 
   1223 
   1224 if __name__ == '__main__':
   1225   try:
   1226     main(sys.argv[1:])
   1227   except common.ExternalError as e:
   1228     print("\n   ERROR: %s\n" % (e,))
   1229     sys.exit(1)
   1230   finally:
   1231     common.Cleanup()
   1232