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