github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/make/tools/releasetools/sign_target_files_apks.py (about)

     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()