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