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 name/key pairs as though they appeared in
     25       apkcerts.txt (so mappings specified by -k and -d are applied).
     26       Keys specified in -e override any value for that app contained
     27       in the apkcerts.txt file.  Option may be repeated to give
     28       multiple extra packages.
     29 
     30   -k  (--key_mapping)  <src_key=dest_key>
     31       Add a mapping from the key name as specified in apkcerts.txt (the
     32       src_key) to the real key you wish to sign the package with
     33       (dest_key).  Option may be repeated to give multiple key
     34       mappings.
     35 
     36   -d  (--default_key_mappings)  <dir>
     37       Set up the following key mappings:
     38 
     39         $devkey/devkey    ==>  $dir/releasekey
     40         $devkey/testkey   ==>  $dir/releasekey
     41         $devkey/media     ==>  $dir/media
     42         $devkey/shared    ==>  $dir/shared
     43         $devkey/platform  ==>  $dir/platform
     44 
     45       where $devkey is the directory part of the value of
     46       default_system_dev_certificate from the input target-files's
     47       META/misc_info.txt.  (Defaulting to "build/target/product/security"
     48       if the value is not present in misc_info.
     49 
     50       -d and -k options are added to the set of mappings in the order
     51       in which they appear on the command line.
     52 
     53   -o  (--replace_ota_keys)
     54       Replace the certificate (public key) used by OTA package verification
     55       with the ones specified in the input target_files zip (in the
     56       META/otakeys.txt file). Key remapping (-k and -d) is performed on the
     57       keys. For A/B devices, the payload verification key will be replaced
     58       as well. If there're multiple OTA keys, only the first one will be used
     59       for payload verification.
     60 
     61   -t  (--tag_changes)  <+tag>,<-tag>,...
     62       Comma-separated list of changes to make to the set of tags (in
     63       the last component of the build fingerprint).  Prefix each with
     64       '+' or '-' to indicate whether that tag should be added or
     65       removed.  Changes are processed in the order they appear.
     66       Default value is "-test-keys,-dev-keys,+release-keys".
     67 
     68   --replace_verity_private_key <key>
     69       Replace the private key used for verity signing. It expects a filename
     70       WITHOUT the extension (e.g. verity_key).
     71 
     72   --replace_verity_public_key <key>
     73       Replace the certificate (public key) used for verity verification. The
     74       key file replaces the one at BOOT/RAMDISK/verity_key (or ROOT/verity_key
     75       for devices using system_root_image). It expects the key filename WITH
     76       the extension (e.g. verity_key.pub).
     77 
     78   --replace_verity_keyid <path_to_X509_PEM_cert_file>
     79       Replace the veritykeyid in BOOT/cmdline of input_target_file_zip
     80       with keyid of the cert pointed by <path_to_X509_PEM_cert_file>.
     81 
     82   --avb_{boot,system,vendor,dtbo,vbmeta}_algorithm <algorithm>
     83   --avb_{boot,system,vendor,dtbo,vbmeta}_key <key>
     84       Use the specified algorithm (e.g. SHA256_RSA4096) and the key to AVB-sign
     85       the specified image. Otherwise it uses the existing values in info dict.
     86 
     87   --avb_{boot,system,vendor,dtbo,vbmeta}_extra_args <args>
     88       Specify any additional args that are needed to AVB-sign the image
     89       (e.g. "--signing_helper /path/to/helper"). The args will be appended to
     90       the existing ones in info dict.
     91 """
     92 
     93 from __future__ import print_function
     94 
     95 import base64
     96 import copy
     97 import errno
     98 import gzip
     99 import os
    100 import re
    101 import shutil
    102 import stat
    103 import subprocess
    104 import sys
    105 import tempfile
    106 import zipfile
    107 from xml.etree import ElementTree
    108 
    109 import add_img_to_target_files
    110 import common
    111 
    112 
    113 if sys.hexversion < 0x02070000:
    114   print("Python 2.7 or newer is required.", file=sys.stderr)
    115   sys.exit(1)
    116 
    117 
    118 OPTIONS = common.OPTIONS
    119 
    120 OPTIONS.extra_apks = {}
    121 OPTIONS.key_map = {}
    122 OPTIONS.rebuild_recovery = False
    123 OPTIONS.replace_ota_keys = False
    124 OPTIONS.replace_verity_public_key = False
    125 OPTIONS.replace_verity_private_key = False
    126 OPTIONS.replace_verity_keyid = False
    127 OPTIONS.tag_changes = ("-test-keys", "-dev-keys", "+release-keys")
    128 OPTIONS.avb_keys = {}
    129 OPTIONS.avb_algorithms = {}
    130 OPTIONS.avb_extra_args = {}
    131 
    132 
    133 def GetApkCerts(certmap):
    134   # apply the key remapping to the contents of the file
    135   for apk, cert in certmap.iteritems():
    136     certmap[apk] = OPTIONS.key_map.get(cert, cert)
    137 
    138   # apply all the -e options, overriding anything in the file
    139   for apk, cert in OPTIONS.extra_apks.iteritems():
    140     if not cert:
    141       cert = "PRESIGNED"
    142     certmap[apk] = OPTIONS.key_map.get(cert, cert)
    143 
    144   return certmap
    145 
    146 
    147 def CheckAllApksSigned(input_tf_zip, apk_key_map, compressed_extension):
    148   """Check that all the APKs we want to sign have keys specified, and
    149   error out if they don't."""
    150   unknown_apks = []
    151   compressed_apk_extension = None
    152   if compressed_extension:
    153     compressed_apk_extension = ".apk" + compressed_extension
    154   for info in input_tf_zip.infolist():
    155     if (info.filename.endswith(".apk") or
    156         (compressed_apk_extension and
    157          info.filename.endswith(compressed_apk_extension))):
    158       name = os.path.basename(info.filename)
    159       if compressed_apk_extension and name.endswith(compressed_apk_extension):
    160         name = name[:-len(compressed_extension)]
    161       if name not in apk_key_map:
    162         unknown_apks.append(name)
    163   if unknown_apks:
    164     print("ERROR: no key specified for:\n")
    165     print("  " + "\n  ".join(unknown_apks))
    166     print("\nUse '-e <apkname>=' to specify a key (which may be an empty "
    167           "string to not sign this apk).")
    168     sys.exit(1)
    169 
    170 
    171 def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map,
    172             is_compressed):
    173   unsigned = tempfile.NamedTemporaryFile()
    174   unsigned.write(data)
    175   unsigned.flush()
    176 
    177   if is_compressed:
    178     uncompressed = tempfile.NamedTemporaryFile()
    179     with gzip.open(unsigned.name, "rb") as in_file, \
    180          open(uncompressed.name, "wb") as out_file:
    181       shutil.copyfileobj(in_file, out_file)
    182 
    183     # Finally, close the "unsigned" file (which is gzip compressed), and then
    184     # replace it with the uncompressed version.
    185     #
    186     # TODO(narayan): All this nastiness can be avoided if python 3.2 is in use,
    187     # we could just gzip / gunzip in-memory buffers instead.
    188     unsigned.close()
    189     unsigned = uncompressed
    190 
    191   signed = tempfile.NamedTemporaryFile()
    192 
    193   # For pre-N builds, don't upgrade to SHA-256 JAR signatures based on the APK's
    194   # minSdkVersion to avoid increasing incremental OTA update sizes. If an APK
    195   # didn't change, we don't want its signature to change due to the switch
    196   # from SHA-1 to SHA-256.
    197   # By default, APK signer chooses SHA-256 signatures if the APK's minSdkVersion
    198   # is 18 or higher. For pre-N builds we disable this mechanism by pretending
    199   # that the APK's minSdkVersion is 1.
    200   # For N+ builds, we let APK signer rely on the APK's minSdkVersion to
    201   # determine whether to use SHA-256.
    202   min_api_level = None
    203   if platform_api_level > 23:
    204     # Let APK signer choose whether to use SHA-1 or SHA-256, based on the APK's
    205     # minSdkVersion attribute
    206     min_api_level = None
    207   else:
    208     # Force APK signer to use SHA-1
    209     min_api_level = 1
    210 
    211   common.SignFile(unsigned.name, signed.name, keyname, pw,
    212                   min_api_level=min_api_level,
    213                   codename_to_api_level_map=codename_to_api_level_map)
    214 
    215   data = None
    216   if is_compressed:
    217     # Recompress the file after it has been signed.
    218     compressed = tempfile.NamedTemporaryFile()
    219     with open(signed.name, "rb") as in_file, \
    220          gzip.open(compressed.name, "wb") as out_file:
    221       shutil.copyfileobj(in_file, out_file)
    222 
    223     data = compressed.read()
    224     compressed.close()
    225   else:
    226     data = signed.read()
    227 
    228   unsigned.close()
    229   signed.close()
    230 
    231   return data
    232 
    233 
    234 def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
    235                        apk_key_map, key_passwords, platform_api_level,
    236                        codename_to_api_level_map,
    237                        compressed_extension):
    238 
    239   compressed_apk_extension = None
    240   if compressed_extension:
    241     compressed_apk_extension = ".apk" + compressed_extension
    242 
    243   maxsize = max(
    244       [len(os.path.basename(i.filename)) for i in input_tf_zip.infolist()
    245        if (i.filename.endswith('.apk') or
    246            (compressed_apk_extension and
    247             i.filename.endswith(compressed_apk_extension)))])
    248   system_root_image = misc_info.get("system_root_image") == "true"
    249 
    250   for info in input_tf_zip.infolist():
    251     if info.filename.startswith("IMAGES/"):
    252       continue
    253 
    254     data = input_tf_zip.read(info.filename)
    255     out_info = copy.copy(info)
    256 
    257     # Sign APKs.
    258     if (info.filename.endswith(".apk") or
    259         (compressed_apk_extension and
    260          info.filename.endswith(compressed_apk_extension))):
    261       is_compressed = (compressed_extension and
    262                        info.filename.endswith(compressed_apk_extension))
    263       name = os.path.basename(info.filename)
    264       if is_compressed:
    265         name = name[:-len(compressed_extension)]
    266 
    267       key = apk_key_map[name]
    268       if key not in common.SPECIAL_CERT_STRINGS:
    269         print("    signing: %-*s (%s)" % (maxsize, name, key))
    270         signed_data = SignApk(data, key, key_passwords[key], platform_api_level,
    271                               codename_to_api_level_map, is_compressed)
    272         common.ZipWriteStr(output_tf_zip, out_info, signed_data)
    273       else:
    274         # an APK we're not supposed to sign.
    275         print("NOT signing: %s" % (name,))
    276         common.ZipWriteStr(output_tf_zip, out_info, data)
    277 
    278     # System properties.
    279     elif info.filename in ("SYSTEM/build.prop",
    280                            "VENDOR/build.prop",
    281                            "SYSTEM/etc/prop.default",
    282                            "BOOT/RAMDISK/prop.default",
    283                            "BOOT/RAMDISK/default.prop",  # legacy
    284                            "ROOT/default.prop",  # legacy
    285                            "RECOVERY/RAMDISK/prop.default",
    286                            "RECOVERY/RAMDISK/default.prop"):  # legacy
    287       print("Rewriting %s:" % (info.filename,))
    288       if stat.S_ISLNK(info.external_attr >> 16):
    289         new_data = data
    290       else:
    291         new_data = RewriteProps(data)
    292       common.ZipWriteStr(output_tf_zip, out_info, new_data)
    293 
    294     # Replace the certs in *mac_permissions.xml (there could be multiple, such
    295     # as {system,vendor}/etc/selinux/{plat,nonplat}_mac_permissions.xml).
    296     elif info.filename.endswith("mac_permissions.xml"):
    297       print("Rewriting %s with new keys." % (info.filename,))
    298       new_data = ReplaceCerts(data)
    299       common.ZipWriteStr(output_tf_zip, out_info, new_data)
    300 
    301     # Ask add_img_to_target_files to rebuild the recovery patch if needed.
    302     elif info.filename in ("SYSTEM/recovery-from-boot.p",
    303                            "SYSTEM/etc/recovery.img",
    304                            "SYSTEM/bin/install-recovery.sh"):
    305       OPTIONS.rebuild_recovery = True
    306 
    307     # Don't copy OTA keys if we're replacing them.
    308     elif (OPTIONS.replace_ota_keys and
    309           info.filename in (
    310               "BOOT/RAMDISK/res/keys",
    311               "BOOT/RAMDISK/etc/update_engine/update-payload-key.pub.pem",
    312               "RECOVERY/RAMDISK/res/keys",
    313               "SYSTEM/etc/security/otacerts.zip",
    314               "SYSTEM/etc/update_engine/update-payload-key.pub.pem")):
    315       pass
    316 
    317     # Skip META/misc_info.txt since we will write back the new values later.
    318     elif info.filename == "META/misc_info.txt":
    319       pass
    320 
    321     # Skip verity public key if we will replace it.
    322     elif (OPTIONS.replace_verity_public_key and
    323           info.filename in ("BOOT/RAMDISK/verity_key",
    324                             "ROOT/verity_key")):
    325       pass
    326 
    327     # Skip verity keyid (for system_root_image use) if we will replace it.
    328     elif (OPTIONS.replace_verity_keyid and
    329           info.filename == "BOOT/cmdline"):
    330       pass
    331 
    332     # Skip the care_map as we will regenerate the system/vendor images.
    333     elif info.filename == "META/care_map.txt":
    334       pass
    335 
    336     # A non-APK file; copy it verbatim.
    337     else:
    338       common.ZipWriteStr(output_tf_zip, out_info, data)
    339 
    340   if OPTIONS.replace_ota_keys:
    341     ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info)
    342 
    343   # Replace the keyid string in misc_info dict.
    344   if OPTIONS.replace_verity_private_key:
    345     ReplaceVerityPrivateKey(misc_info, OPTIONS.replace_verity_private_key[1])
    346 
    347   if OPTIONS.replace_verity_public_key:
    348     dest = "ROOT/verity_key" if system_root_image else "BOOT/RAMDISK/verity_key"
    349     # We are replacing the one in boot image only, since the one under
    350     # recovery won't ever be needed.
    351     ReplaceVerityPublicKey(
    352         output_tf_zip, dest, OPTIONS.replace_verity_public_key[1])
    353 
    354   # Replace the keyid string in BOOT/cmdline.
    355   if OPTIONS.replace_verity_keyid:
    356     ReplaceVerityKeyId(input_tf_zip, output_tf_zip,
    357                        OPTIONS.replace_verity_keyid[1])
    358 
    359   # Replace the AVB signing keys, if any.
    360   ReplaceAvbSigningKeys(misc_info)
    361 
    362   # Write back misc_info with the latest values.
    363   ReplaceMiscInfoTxt(input_tf_zip, output_tf_zip, misc_info)
    364 
    365 
    366 def ReplaceCerts(data):
    367   """Replaces all the occurences of X.509 certs with the new ones.
    368 
    369   The mapping info is read from OPTIONS.key_map. Non-existent certificate will
    370   be skipped. After the replacement, it additionally checks for duplicate
    371   entries, which would otherwise fail the policy loading code in
    372   frameworks/base/services/core/java/com/android/server/pm/SELinuxMMAC.java.
    373 
    374   Args:
    375     data: Input string that contains a set of X.509 certs.
    376 
    377   Returns:
    378     A string after the replacement.
    379 
    380   Raises:
    381     AssertionError: On finding duplicate entries.
    382   """
    383   for old, new in OPTIONS.key_map.iteritems():
    384     if OPTIONS.verbose:
    385       print("    Replacing %s.x509.pem with %s.x509.pem" % (old, new))
    386 
    387     try:
    388       with open(old + ".x509.pem") as old_fp:
    389         old_cert16 = base64.b16encode(
    390             common.ParseCertificate(old_fp.read())).lower()
    391       with open(new + ".x509.pem") as new_fp:
    392         new_cert16 = base64.b16encode(
    393             common.ParseCertificate(new_fp.read())).lower()
    394     except IOError as e:
    395       if OPTIONS.verbose or e.errno != errno.ENOENT:
    396         print("    Error accessing %s: %s.\nSkip replacing %s.x509.pem with "
    397               "%s.x509.pem." % (e.filename, e.strerror, old, new))
    398       continue
    399 
    400     # Only match entire certs.
    401     pattern = "\\b" + old_cert16 + "\\b"
    402     (data, num) = re.subn(pattern, new_cert16, data, flags=re.IGNORECASE)
    403 
    404     if OPTIONS.verbose:
    405       print("    Replaced %d occurence(s) of %s.x509.pem with %s.x509.pem" % (
    406           num, old, new))
    407 
    408   # Verify that there're no duplicate entries after the replacement. Note that
    409   # it's only checking entries with global seinfo at the moment (i.e. ignoring
    410   # the ones with inner packages). (Bug: 69479366)
    411   root = ElementTree.fromstring(data)
    412   signatures = [signer.attrib['signature'] for signer in root.findall('signer')]
    413   assert len(signatures) == len(set(signatures)), \
    414       "Found duplicate entries after cert replacement: {}".format(data)
    415 
    416   return data
    417 
    418 
    419 def EditTags(tags):
    420   """Applies the edits to the tag string as specified in OPTIONS.tag_changes.
    421 
    422   Args:
    423     tags: The input string that contains comma-separated tags.
    424 
    425   Returns:
    426     The updated tags (comma-separated and sorted).
    427   """
    428   tags = set(tags.split(","))
    429   for ch in OPTIONS.tag_changes:
    430     if ch[0] == "-":
    431       tags.discard(ch[1:])
    432     elif ch[0] == "+":
    433       tags.add(ch[1:])
    434   return ",".join(sorted(tags))
    435 
    436 
    437 def RewriteProps(data):
    438   """Rewrites the system properties in the given string.
    439 
    440   Each property is expected in 'key=value' format. The properties that contain
    441   build tags (i.e. test-keys, dev-keys) will be updated accordingly by calling
    442   EditTags().
    443 
    444   Args:
    445     data: Input string, separated by newlines.
    446 
    447   Returns:
    448     The string with modified properties.
    449   """
    450   output = []
    451   for line in data.split("\n"):
    452     line = line.strip()
    453     original_line = line
    454     if line and line[0] != '#' and "=" in line:
    455       key, value = line.split("=", 1)
    456       if key in ("ro.build.fingerprint", "ro.build.thumbprint",
    457                  "ro.vendor.build.fingerprint", "ro.vendor.build.thumbprint"):
    458         pieces = value.split("/")
    459         pieces[-1] = EditTags(pieces[-1])
    460         value = "/".join(pieces)
    461       elif key == "ro.bootimage.build.fingerprint":
    462         pieces = value.split("/")
    463         pieces[-1] = EditTags(pieces[-1])
    464         value = "/".join(pieces)
    465       elif key == "ro.build.description":
    466         pieces = value.split(" ")
    467         assert len(pieces) == 5
    468         pieces[-1] = EditTags(pieces[-1])
    469         value = " ".join(pieces)
    470       elif key == "ro.build.tags":
    471         value = EditTags(value)
    472       elif key == "ro.build.display.id":
    473         # change, eg, "JWR66N dev-keys" to "JWR66N"
    474         value = value.split()
    475         if len(value) > 1 and value[-1].endswith("-keys"):
    476           value.pop()
    477         value = " ".join(value)
    478       line = key + "=" + value
    479     if line != original_line:
    480       print("  replace: ", original_line)
    481       print("     with: ", line)
    482     output.append(line)
    483   return "\n".join(output) + "\n"
    484 
    485 
    486 def ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info):
    487   try:
    488     keylist = input_tf_zip.read("META/otakeys.txt").split()
    489   except KeyError:
    490     raise common.ExternalError("can't read META/otakeys.txt from input")
    491 
    492   extra_recovery_keys = misc_info.get("extra_recovery_keys")
    493   if extra_recovery_keys:
    494     extra_recovery_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem"
    495                            for k in extra_recovery_keys.split()]
    496     if extra_recovery_keys:
    497       print("extra recovery-only key(s): " + ", ".join(extra_recovery_keys))
    498   else:
    499     extra_recovery_keys = []
    500 
    501   mapped_keys = []
    502   for k in keylist:
    503     m = re.match(r"^(.*)\.x509\.pem$", k)
    504     if not m:
    505       raise common.ExternalError(
    506           "can't parse \"%s\" from META/otakeys.txt" % (k,))
    507     k = m.group(1)
    508     mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem")
    509 
    510   if mapped_keys:
    511     print("using:\n   ", "\n   ".join(mapped_keys))
    512     print("for OTA package verification")
    513   else:
    514     devkey = misc_info.get("default_system_dev_certificate",
    515                            "build/target/product/security/testkey")
    516     mapped_devkey = OPTIONS.key_map.get(devkey, devkey)
    517     if mapped_devkey != devkey:
    518       misc_info["default_system_dev_certificate"] = mapped_devkey
    519     mapped_keys.append(mapped_devkey + ".x509.pem")
    520     print("META/otakeys.txt has no keys; using %s for OTA package"
    521           " verification." % (mapped_keys[0],))
    522 
    523   # recovery uses a version of the key that has been slightly
    524   # predigested (by DumpPublicKey.java) and put in res/keys.
    525   # extra_recovery_keys are used only in recovery.
    526   cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
    527          ["-jar",
    528           os.path.join(OPTIONS.search_path, "framework", "dumpkey.jar")] +
    529          mapped_keys + extra_recovery_keys)
    530   p = common.Run(cmd, stdout=subprocess.PIPE)
    531   new_recovery_keys, _ = p.communicate()
    532   if p.returncode != 0:
    533     raise common.ExternalError("failed to run dumpkeys")
    534 
    535   # system_root_image puts the recovery keys at BOOT/RAMDISK.
    536   if misc_info.get("system_root_image") == "true":
    537     recovery_keys_location = "BOOT/RAMDISK/res/keys"
    538   else:
    539     recovery_keys_location = "RECOVERY/RAMDISK/res/keys"
    540   common.ZipWriteStr(output_tf_zip, recovery_keys_location, new_recovery_keys)
    541 
    542   # SystemUpdateActivity uses the x509.pem version of the keys, but
    543   # put into a zipfile system/etc/security/otacerts.zip.
    544   # We DO NOT include the extra_recovery_keys (if any) here.
    545 
    546   try:
    547     from StringIO import StringIO
    548   except ImportError:
    549     from io import StringIO
    550   temp_file = StringIO()
    551   certs_zip = zipfile.ZipFile(temp_file, "w")
    552   for k in mapped_keys:
    553     common.ZipWrite(certs_zip, k)
    554   common.ZipClose(certs_zip)
    555   common.ZipWriteStr(output_tf_zip, "SYSTEM/etc/security/otacerts.zip",
    556                      temp_file.getvalue())
    557 
    558   # For A/B devices, update the payload verification key.
    559   if misc_info.get("ab_update") == "true":
    560     # Unlike otacerts.zip that may contain multiple keys, we can only specify
    561     # ONE payload verification key.
    562     if len(mapped_keys) > 1:
    563       print("\n  WARNING: Found more than one OTA keys; Using the first one"
    564             " as payload verification key.\n\n")
    565 
    566     print("Using %s for payload verification." % (mapped_keys[0],))
    567     pubkey = common.ExtractPublicKey(mapped_keys[0])
    568     common.ZipWriteStr(
    569         output_tf_zip,
    570         "SYSTEM/etc/update_engine/update-payload-key.pub.pem",
    571         pubkey)
    572     common.ZipWriteStr(
    573         output_tf_zip,
    574         "BOOT/RAMDISK/etc/update_engine/update-payload-key.pub.pem",
    575         pubkey)
    576 
    577   return new_recovery_keys
    578 
    579 
    580 def ReplaceVerityPublicKey(output_zip, filename, key_path):
    581   """Replaces the verity public key at the given path in the given zip.
    582 
    583   Args:
    584     output_zip: The output target_files zip.
    585     filename: The archive name in the output zip.
    586     key_path: The path to the public key.
    587   """
    588   print("Replacing verity public key with %s" % (key_path,))
    589   common.ZipWrite(output_zip, key_path, arcname=filename)
    590 
    591 
    592 def ReplaceVerityPrivateKey(misc_info, key_path):
    593   """Replaces the verity private key in misc_info dict.
    594 
    595   Args:
    596     misc_info: The info dict.
    597     key_path: The path to the private key in PKCS#8 format.
    598   """
    599   print("Replacing verity private key with %s" % (key_path,))
    600   misc_info["verity_key"] = key_path
    601 
    602 
    603 def ReplaceVerityKeyId(input_zip, output_zip, key_path):
    604   """Replaces the veritykeyid parameter in BOOT/cmdline.
    605 
    606   Args:
    607     input_zip: The input target_files zip, which should be already open.
    608     output_zip: The output target_files zip, which should be already open and
    609         writable.
    610     key_path: The path to the PEM encoded X.509 certificate.
    611   """
    612   in_cmdline = input_zip.read("BOOT/cmdline")
    613   # Copy in_cmdline to output_zip if veritykeyid is not present.
    614   if "veritykeyid" not in in_cmdline:
    615     common.ZipWriteStr(output_zip, "BOOT/cmdline", in_cmdline)
    616     return
    617 
    618   out_buffer = []
    619   for param in in_cmdline.split():
    620     if "veritykeyid" not in param:
    621       out_buffer.append(param)
    622       continue
    623 
    624     # Extract keyid using openssl command.
    625     p = common.Run(["openssl", "x509", "-in", key_path, "-text"],
    626                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    627     keyid, stderr = p.communicate()
    628     assert p.returncode == 0, "Failed to dump certificate: {}".format(stderr)
    629     keyid = re.search(
    630         r'keyid:([0-9a-fA-F:]*)', keyid).group(1).replace(':', '').lower()
    631     print("Replacing verity keyid with {}".format(keyid))
    632     out_buffer.append("veritykeyid=id:%s" % (keyid,))
    633 
    634   out_cmdline = ' '.join(out_buffer).strip() + '\n'
    635   common.ZipWriteStr(output_zip, "BOOT/cmdline", out_cmdline)
    636 
    637 
    638 def ReplaceMiscInfoTxt(input_zip, output_zip, misc_info):
    639   """Replaces META/misc_info.txt.
    640 
    641   Only writes back the ones in the original META/misc_info.txt. Because the
    642   current in-memory dict contains additional items computed at runtime.
    643   """
    644   misc_info_old = common.LoadDictionaryFromLines(
    645       input_zip.read('META/misc_info.txt').split('\n'))
    646   items = []
    647   for key in sorted(misc_info):
    648     if key in misc_info_old:
    649       items.append('%s=%s' % (key, misc_info[key]))
    650   common.ZipWriteStr(output_zip, "META/misc_info.txt", '\n'.join(items))
    651 
    652 
    653 def ReplaceAvbSigningKeys(misc_info):
    654   """Replaces the AVB signing keys."""
    655 
    656   AVB_FOOTER_ARGS_BY_PARTITION = {
    657       'boot' : 'avb_boot_add_hash_footer_args',
    658       'dtbo' : 'avb_dtbo_add_hash_footer_args',
    659       'recovery' : 'avb_recovery_add_hash_footer_args',
    660       'system' : 'avb_system_add_hashtree_footer_args',
    661       'vendor' : 'avb_vendor_add_hashtree_footer_args',
    662       'vbmeta' : 'avb_vbmeta_args',
    663   }
    664 
    665   def ReplaceAvbPartitionSigningKey(partition):
    666     key = OPTIONS.avb_keys.get(partition)
    667     if not key:
    668       return
    669 
    670     algorithm = OPTIONS.avb_algorithms.get(partition)
    671     assert algorithm, 'Missing AVB signing algorithm for %s' % (partition,)
    672 
    673     print('Replacing AVB signing key for %s with "%s" (%s)' % (
    674         partition, key, algorithm))
    675     misc_info['avb_' + partition + '_algorithm'] = algorithm
    676     misc_info['avb_' + partition + '_key_path'] = key
    677 
    678     extra_args = OPTIONS.avb_extra_args.get(partition)
    679     if extra_args:
    680       print('Setting extra AVB signing args for %s to "%s"' % (
    681           partition, extra_args))
    682       args_key = AVB_FOOTER_ARGS_BY_PARTITION[partition]
    683       misc_info[args_key] = (misc_info.get(args_key, '') + ' ' + extra_args)
    684 
    685   for partition in AVB_FOOTER_ARGS_BY_PARTITION:
    686     ReplaceAvbPartitionSigningKey(partition)
    687 
    688 
    689 def BuildKeyMap(misc_info, key_mapping_options):
    690   for s, d in key_mapping_options:
    691     if s is None:   # -d option
    692       devkey = misc_info.get("default_system_dev_certificate",
    693                              "build/target/product/security/testkey")
    694       devkeydir = os.path.dirname(devkey)
    695 
    696       OPTIONS.key_map.update({
    697           devkeydir + "/testkey":  d + "/releasekey",
    698           devkeydir + "/devkey":   d + "/releasekey",
    699           devkeydir + "/media":    d + "/media",
    700           devkeydir + "/shared":   d + "/shared",
    701           devkeydir + "/platform": d + "/platform",
    702           })
    703     else:
    704       OPTIONS.key_map[s] = d
    705 
    706 
    707 def GetApiLevelAndCodename(input_tf_zip):
    708   data = input_tf_zip.read("SYSTEM/build.prop")
    709   api_level = None
    710   codename = None
    711   for line in data.split("\n"):
    712     line = line.strip()
    713     if line and line[0] != '#' and "=" in line:
    714       key, value = line.split("=", 1)
    715       key = key.strip()
    716       if key == "ro.build.version.sdk":
    717         api_level = int(value.strip())
    718       elif key == "ro.build.version.codename":
    719         codename = value.strip()
    720 
    721   if api_level is None:
    722     raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
    723   if codename is None:
    724     raise ValueError("No ro.build.version.codename in SYSTEM/build.prop")
    725 
    726   return (api_level, codename)
    727 
    728 
    729 def GetCodenameToApiLevelMap(input_tf_zip):
    730   data = input_tf_zip.read("SYSTEM/build.prop")
    731   api_level = None
    732   codenames = None
    733   for line in data.split("\n"):
    734     line = line.strip()
    735     if line and line[0] != '#' and "=" in line:
    736       key, value = line.split("=", 1)
    737       key = key.strip()
    738       if key == "ro.build.version.sdk":
    739         api_level = int(value.strip())
    740       elif key == "ro.build.version.all_codenames":
    741         codenames = value.strip().split(",")
    742 
    743   if api_level is None:
    744     raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
    745   if codenames is None:
    746     raise ValueError("No ro.build.version.all_codenames in SYSTEM/build.prop")
    747 
    748   result = dict()
    749   for codename in codenames:
    750     codename = codename.strip()
    751     if len(codename) > 0:
    752       result[codename] = api_level
    753   return result
    754 
    755 
    756 def main(argv):
    757 
    758   key_mapping_options = []
    759 
    760   def option_handler(o, a):
    761     if o in ("-e", "--extra_apks"):
    762       names, key = a.split("=")
    763       names = names.split(",")
    764       for n in names:
    765         OPTIONS.extra_apks[n] = key
    766     elif o in ("-d", "--default_key_mappings"):
    767       key_mapping_options.append((None, a))
    768     elif o in ("-k", "--key_mapping"):
    769       key_mapping_options.append(a.split("=", 1))
    770     elif o in ("-o", "--replace_ota_keys"):
    771       OPTIONS.replace_ota_keys = True
    772     elif o in ("-t", "--tag_changes"):
    773       new = []
    774       for i in a.split(","):
    775         i = i.strip()
    776         if not i or i[0] not in "-+":
    777           raise ValueError("Bad tag change '%s'" % (i,))
    778         new.append(i[0] + i[1:].strip())
    779       OPTIONS.tag_changes = tuple(new)
    780     elif o == "--replace_verity_public_key":
    781       OPTIONS.replace_verity_public_key = (True, a)
    782     elif o == "--replace_verity_private_key":
    783       OPTIONS.replace_verity_private_key = (True, a)
    784     elif o == "--replace_verity_keyid":
    785       OPTIONS.replace_verity_keyid = (True, a)
    786     elif o == "--avb_vbmeta_key":
    787       OPTIONS.avb_keys['vbmeta'] = a
    788     elif o == "--avb_vbmeta_algorithm":
    789       OPTIONS.avb_algorithms['vbmeta'] = a
    790     elif o == "--avb_vbmeta_extra_args":
    791       OPTIONS.avb_extra_args['vbmeta'] = a
    792     elif o == "--avb_boot_key":
    793       OPTIONS.avb_keys['boot'] = a
    794     elif o == "--avb_boot_algorithm":
    795       OPTIONS.avb_algorithms['boot'] = a
    796     elif o == "--avb_boot_extra_args":
    797       OPTIONS.avb_extra_args['boot'] = a
    798     elif o == "--avb_dtbo_key":
    799       OPTIONS.avb_keys['dtbo'] = a
    800     elif o == "--avb_dtbo_algorithm":
    801       OPTIONS.avb_algorithms['dtbo'] = a
    802     elif o == "--avb_dtbo_extra_args":
    803       OPTIONS.avb_extra_args['dtbo'] = a
    804     elif o == "--avb_system_key":
    805       OPTIONS.avb_keys['system'] = a
    806     elif o == "--avb_system_algorithm":
    807       OPTIONS.avb_algorithms['system'] = a
    808     elif o == "--avb_system_extra_args":
    809       OPTIONS.avb_extra_args['system'] = a
    810     elif o == "--avb_vendor_key":
    811       OPTIONS.avb_keys['vendor'] = a
    812     elif o == "--avb_vendor_algorithm":
    813       OPTIONS.avb_algorithms['vendor'] = a
    814     elif o == "--avb_vendor_extra_args":
    815       OPTIONS.avb_extra_args['vendor'] = a
    816     else:
    817       return False
    818     return True
    819 
    820   args = common.ParseOptions(
    821       argv, __doc__,
    822       extra_opts="e:d:k:ot:",
    823       extra_long_opts=[
    824           "extra_apks=",
    825           "default_key_mappings=",
    826           "key_mapping=",
    827           "replace_ota_keys",
    828           "tag_changes=",
    829           "replace_verity_public_key=",
    830           "replace_verity_private_key=",
    831           "replace_verity_keyid=",
    832           "avb_vbmeta_algorithm=",
    833           "avb_vbmeta_key=",
    834           "avb_vbmeta_extra_args=",
    835           "avb_boot_algorithm=",
    836           "avb_boot_key=",
    837           "avb_boot_extra_args=",
    838           "avb_dtbo_algorithm=",
    839           "avb_dtbo_key=",
    840           "avb_dtbo_extra_args=",
    841           "avb_system_algorithm=",
    842           "avb_system_key=",
    843           "avb_system_extra_args=",
    844           "avb_vendor_algorithm=",
    845           "avb_vendor_key=",
    846           "avb_vendor_extra_args=",
    847       ],
    848       extra_option_handler=option_handler)
    849 
    850   if len(args) != 2:
    851     common.Usage(__doc__)
    852     sys.exit(1)
    853 
    854   input_zip = zipfile.ZipFile(args[0], "r")
    855   output_zip = zipfile.ZipFile(args[1], "w",
    856                                compression=zipfile.ZIP_DEFLATED,
    857                                allowZip64=True)
    858 
    859   misc_info = common.LoadInfoDict(input_zip)
    860 
    861   BuildKeyMap(misc_info, key_mapping_options)
    862 
    863   certmap, compressed_extension = common.ReadApkCerts(input_zip)
    864   apk_key_map = GetApkCerts(certmap)
    865   CheckAllApksSigned(input_zip, apk_key_map, compressed_extension)
    866 
    867   key_passwords = common.GetKeyPasswords(set(apk_key_map.values()))
    868   platform_api_level, _ = GetApiLevelAndCodename(input_zip)
    869   codename_to_api_level_map = GetCodenameToApiLevelMap(input_zip)
    870 
    871   ProcessTargetFiles(input_zip, output_zip, misc_info,
    872                      apk_key_map, key_passwords,
    873                      platform_api_level,
    874                      codename_to_api_level_map,
    875                      compressed_extension)
    876 
    877   common.ZipClose(input_zip)
    878   common.ZipClose(output_zip)
    879 
    880   # Skip building userdata.img and cache.img when signing the target files.
    881   new_args = ["--is_signing"]
    882   # add_img_to_target_files builds the system image from scratch, so the
    883   # recovery patch is guaranteed to be regenerated there.
    884   if OPTIONS.rebuild_recovery:
    885     new_args.append("--rebuild_recovery")
    886   new_args.append(args[1])
    887   add_img_to_target_files.main(new_args)
    888 
    889   print("done.")
    890 
    891 
    892 if __name__ == '__main__':
    893   try:
    894     main(sys.argv[1:])
    895   except common.ExternalError as e:
    896     print("\n   ERROR: %s\n" % (e,))
    897     sys.exit(1)
    898   finally:
    899     common.Cleanup()
    900