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