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         build/target/product/security/testkey   ==>  $dir/releasekey
     40         build/target/product/security/media     ==>  $dir/media
     41         build/target/product/security/shared    ==>  $dir/shared
     42         build/target/product/security/platform  ==>  $dir/platform
     43 
     44       -d and -k options are added to the set of mappings in the order
     45       in which they appear on the command line.
     46 
     47   -o  (--replace_ota_keys)
     48       Replace the certificate (public key) used by OTA package
     49       verification with the one specified in the input target_files
     50       zip (in the META/otakeys.txt file).  Key remapping (-k and -d)
     51       is performed on this key.
     52 
     53   -t  (--tag_changes)  <+tag>,<-tag>,...
     54       Comma-separated list of changes to make to the set of tags (in
     55       the last component of the build fingerprint).  Prefix each with
     56       '+' or '-' to indicate whether that tag should be added or
     57       removed.  Changes are processed in the order they appear.
     58       Default value is "-test-keys,+release-keys".
     59 
     60 """
     61 
     62 import sys
     63 
     64 if sys.hexversion < 0x02040000:
     65   print >> sys.stderr, "Python 2.4 or newer is required."
     66   sys.exit(1)
     67 
     68 import cStringIO
     69 import copy
     70 import os
     71 import re
     72 import subprocess
     73 import tempfile
     74 import zipfile
     75 
     76 import common
     77 
     78 OPTIONS = common.OPTIONS
     79 
     80 OPTIONS.extra_apks = {}
     81 OPTIONS.key_map = {}
     82 OPTIONS.replace_ota_keys = False
     83 OPTIONS.tag_changes = ("-test-keys", "+release-keys")
     84 
     85 def GetApkCerts(tf_zip):
     86   certmap = common.ReadApkCerts(tf_zip)
     87 
     88   # apply the key remapping to the contents of the file
     89   for apk, cert in certmap.iteritems():
     90     certmap[apk] = OPTIONS.key_map.get(cert, cert)
     91 
     92   # apply all the -e options, overriding anything in the file
     93   for apk, cert in OPTIONS.extra_apks.iteritems():
     94     if not cert:
     95       cert = "PRESIGNED"
     96     certmap[apk] = OPTIONS.key_map.get(cert, cert)
     97 
     98   return certmap
     99 
    100 
    101 def CheckAllApksSigned(input_tf_zip, apk_key_map):
    102   """Check that all the APKs we want to sign have keys specified, and
    103   error out if they don't."""
    104   unknown_apks = []
    105   for info in input_tf_zip.infolist():
    106     if info.filename.endswith(".apk"):
    107       name = os.path.basename(info.filename)
    108       if name not in apk_key_map:
    109         unknown_apks.append(name)
    110   if unknown_apks:
    111     print "ERROR: no key specified for:\n\n ",
    112     print "\n  ".join(unknown_apks)
    113     print "\nUse '-e <apkname>=' to specify a key (which may be an"
    114     print "empty string to not sign this apk)."
    115     sys.exit(1)
    116 
    117 
    118 def SignApk(data, keyname, pw):
    119   unsigned = tempfile.NamedTemporaryFile()
    120   unsigned.write(data)
    121   unsigned.flush()
    122 
    123   signed = tempfile.NamedTemporaryFile()
    124 
    125   common.SignFile(unsigned.name, signed.name, keyname, pw, align=4)
    126 
    127   data = signed.read()
    128   unsigned.close()
    129   signed.close()
    130 
    131   return data
    132 
    133 
    134 def SignApks(input_tf_zip, output_tf_zip, apk_key_map, key_passwords):
    135   maxsize = max([len(os.path.basename(i.filename))
    136                  for i in input_tf_zip.infolist()
    137                  if i.filename.endswith('.apk')])
    138 
    139   for info in input_tf_zip.infolist():
    140     data = input_tf_zip.read(info.filename)
    141     out_info = copy.copy(info)
    142     if info.filename.endswith(".apk"):
    143       name = os.path.basename(info.filename)
    144       key = apk_key_map[name]
    145       if key not in common.SPECIAL_CERT_STRINGS:
    146         print "    signing: %-*s (%s)" % (maxsize, name, key)
    147         signed_data = SignApk(data, key, key_passwords[key])
    148         output_tf_zip.writestr(out_info, signed_data)
    149       else:
    150         # an APK we're not supposed to sign.
    151         print "NOT signing: %s" % (name,)
    152         output_tf_zip.writestr(out_info, data)
    153     elif info.filename in ("SYSTEM/build.prop",
    154                            "RECOVERY/RAMDISK/default.prop"):
    155       print "rewriting %s:" % (info.filename,)
    156       new_data = RewriteProps(data)
    157       output_tf_zip.writestr(out_info, new_data)
    158     else:
    159       # a non-APK file; copy it verbatim
    160       output_tf_zip.writestr(out_info, data)
    161 
    162 
    163 def EditTags(tags):
    164   """Given a string containing comma-separated tags, apply the edits
    165   specified in OPTIONS.tag_changes and return the updated string."""
    166   tags = set(tags.split(","))
    167   for ch in OPTIONS.tag_changes:
    168     if ch[0] == "-":
    169       tags.discard(ch[1:])
    170     elif ch[0] == "+":
    171       tags.add(ch[1:])
    172   return ",".join(sorted(tags))
    173 
    174 
    175 def RewriteProps(data):
    176   output = []
    177   for line in data.split("\n"):
    178     line = line.strip()
    179     original_line = line
    180     if line and line[0] != '#':
    181       key, value = line.split("=", 1)
    182       if key == "ro.build.fingerprint":
    183         pieces = value.split("/")
    184         pieces[-1] = EditTags(pieces[-1])
    185         value = "/".join(pieces)
    186       elif key == "ro.build.description":
    187         pieces = value.split(" ")
    188         assert len(pieces) == 5
    189         pieces[-1] = EditTags(pieces[-1])
    190         value = " ".join(pieces)
    191       elif key == "ro.build.tags":
    192         value = EditTags(value)
    193       line = key + "=" + value
    194     if line != original_line:
    195       print "  replace: ", original_line
    196       print "     with: ", line
    197     output.append(line)
    198   return "\n".join(output) + "\n"
    199 
    200 
    201 def ReplaceOtaKeys(input_tf_zip, output_tf_zip):
    202   try:
    203     keylist = input_tf_zip.read("META/otakeys.txt").split()
    204   except KeyError:
    205     raise ExternalError("can't read META/otakeys.txt from input")
    206 
    207   mapped_keys = []
    208   for k in keylist:
    209     m = re.match(r"^(.*)\.x509\.pem$", k)
    210     if not m:
    211       raise ExternalError("can't parse \"%s\" from META/otakeys.txt" % (k,))
    212     k = m.group(1)
    213     mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem")
    214 
    215   if mapped_keys:
    216     print "using:\n   ", "\n   ".join(mapped_keys)
    217     print "for OTA package verification"
    218   else:
    219     mapped_keys.append(
    220         OPTIONS.key_map["build/target/product/security/testkey"] + ".x509.pem")
    221     print "META/otakeys.txt has no keys; using", mapped_keys[0]
    222 
    223   # recovery uses a version of the key that has been slightly
    224   # predigested (by DumpPublicKey.java) and put in res/keys.
    225 
    226   p = common.Run(["java", "-jar",
    227                   os.path.join(OPTIONS.search_path, "framework", "dumpkey.jar")]
    228                  + mapped_keys,
    229                  stdout=subprocess.PIPE)
    230   data, _ = p.communicate()
    231   if p.returncode != 0:
    232     raise ExternalError("failed to run dumpkeys")
    233   common.ZipWriteStr(output_tf_zip, "RECOVERY/RAMDISK/res/keys", data)
    234 
    235   # SystemUpdateActivity uses the x509.pem version of the keys, but
    236   # put into a zipfile system/etc/security/otacerts.zip.
    237 
    238   tempfile = cStringIO.StringIO()
    239   certs_zip = zipfile.ZipFile(tempfile, "w")
    240   for k in mapped_keys:
    241     certs_zip.write(k)
    242   certs_zip.close()
    243   common.ZipWriteStr(output_tf_zip, "SYSTEM/etc/security/otacerts.zip",
    244                      tempfile.getvalue())
    245 
    246 
    247 def main(argv):
    248 
    249   def option_handler(o, a):
    250     if o in ("-e", "--extra_apks"):
    251       names, key = a.split("=")
    252       names = names.split(",")
    253       for n in names:
    254         OPTIONS.extra_apks[n] = key
    255     elif o in ("-d", "--default_key_mappings"):
    256       OPTIONS.key_map.update({
    257           "build/target/product/security/testkey": "%s/releasekey" % (a,),
    258           "build/target/product/security/media": "%s/media" % (a,),
    259           "build/target/product/security/shared": "%s/shared" % (a,),
    260           "build/target/product/security/platform": "%s/platform" % (a,),
    261           })
    262     elif o in ("-k", "--key_mapping"):
    263       s, d = a.split("=")
    264       OPTIONS.key_map[s] = d
    265     elif o in ("-o", "--replace_ota_keys"):
    266       OPTIONS.replace_ota_keys = True
    267     elif o in ("-t", "--tag_changes"):
    268       new = []
    269       for i in a.split(","):
    270         i = i.strip()
    271         if not i or i[0] not in "-+":
    272           raise ValueError("Bad tag change '%s'" % (i,))
    273         new.append(i[0] + i[1:].strip())
    274       OPTIONS.tag_changes = tuple(new)
    275     else:
    276       return False
    277     return True
    278 
    279   args = common.ParseOptions(argv, __doc__,
    280                              extra_opts="e:d:k:ot:",
    281                              extra_long_opts=["extra_apks=",
    282                                               "default_key_mappings=",
    283                                               "key_mapping=",
    284                                               "replace_ota_keys",
    285                                               "tag_changes="],
    286                              extra_option_handler=option_handler)
    287 
    288   if len(args) != 2:
    289     common.Usage(__doc__)
    290     sys.exit(1)
    291 
    292   input_zip = zipfile.ZipFile(args[0], "r")
    293   output_zip = zipfile.ZipFile(args[1], "w")
    294 
    295   apk_key_map = GetApkCerts(input_zip)
    296   CheckAllApksSigned(input_zip, apk_key_map)
    297 
    298   key_passwords = common.GetKeyPasswords(set(apk_key_map.values()))
    299   SignApks(input_zip, output_zip, apk_key_map, key_passwords)
    300 
    301   if OPTIONS.replace_ota_keys:
    302     ReplaceOtaKeys(input_zip, output_zip)
    303 
    304   input_zip.close()
    305   output_zip.close()
    306 
    307   print "done."
    308 
    309 
    310 if __name__ == '__main__':
    311   try:
    312     main(sys.argv[1:])
    313   except common.ExternalError, e:
    314     print
    315     print "   ERROR: %s" % (e,)
    316     print
    317     sys.exit(1)
    318