sign_target_files_apks revision 17aa944001e7ae2425beec75d3ebc280413631ee
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"""
18Signs all the APK files in a target-files zipfile, producing a new
19target-files zip.
20
21Usage:  sign_target_files_apks [flags] input_target_files output_target_files
22
23  -s  (--signapk_jar)  <path>
24      Path of the signapks.jar file used to sign an individual APK
25      file.
26
27  -e  (--extra_apks)  <name,name,...=key>
28      Add extra APK name/key pairs as though they appeared in
29      apkcerts.txt (so mappings specified by -k and -d are applied).
30      Keys specified in -e override any value for that app contained
31      in the apkcerts.txt file.  Option may be repeated to give
32      multiple extra packages.
33
34  -k  (--key_mapping)  <src_key=dest_key>
35      Add a mapping from the key name as specified in apkcerts.txt (the
36      src_key) to the real key you wish to sign the package with
37      (dest_key).  Option may be repeated to give multiple key
38      mappings.
39
40  -d  (--default_key_mappings)  <dir>
41      Set up the following key mappings:
42
43        build/target/product/security/testkey   ==>  $dir/releasekey
44        build/target/product/security/media     ==>  $dir/media
45        build/target/product/security/shared    ==>  $dir/shared
46        build/target/product/security/platform  ==>  $dir/platform
47
48      -d and -k options are added to the set of mappings in the order
49      in which they appear on the command line.
50
51  -o  (--replace_ota_keys)
52      Replace the certificate (public key) used by OTA package
53      verification with the one specified in the input target_files
54      zip (in the META/otakeys.txt file).  Key remapping (-k and -d)
55      is performed on this key.
56
57  -t  (--extra_tag)  <tag>
58      A string which is added to the set of tags in the last component
59      of the build fingerprint.  Option may be repeated to give
60      multiple extra tags.
61"""
62
63import sys
64
65if sys.hexversion < 0x02040000:
66  print >> sys.stderr, "Python 2.4 or newer is required."
67  sys.exit(1)
68
69import cStringIO
70import copy
71import os
72import re
73import subprocess
74import tempfile
75import zipfile
76
77import common
78
79OPTIONS = common.OPTIONS
80
81OPTIONS.extra_apks = {}
82OPTIONS.key_map = {}
83OPTIONS.replace_ota_keys = False
84OPTIONS.extra_tags = []
85
86def GetApkCerts(tf_zip):
87  certmap = {}
88  for line in tf_zip.read("META/apkcerts.txt").split("\n"):
89    line = line.strip()
90    if not line: continue
91    m = re.match(r'^name="(.*)"\s+certificate="(.*)\.x509\.pem"\s+'
92                 r'private_key="\2\.pk8"$', line)
93    if not m:
94      raise SigningError("failed to parse line from apkcerts.txt:\n" + line)
95    certmap[m.group(1)] = OPTIONS.key_map.get(m.group(2), m.group(2))
96  for apk, cert in OPTIONS.extra_apks.iteritems():
97    certmap[apk] = OPTIONS.key_map.get(cert, cert)
98  return certmap
99
100
101def SignApk(data, keyname, pw):
102  unsigned = tempfile.NamedTemporaryFile()
103  unsigned.write(data)
104  unsigned.flush()
105
106  signed = tempfile.NamedTemporaryFile()
107
108  common.SignFile(unsigned.name, signed.name, keyname, pw, align=4)
109
110  data = signed.read()
111  unsigned.close()
112  signed.close()
113
114  return data
115
116
117def SignApks(input_tf_zip, output_tf_zip):
118  apk_key_map = GetApkCerts(input_tf_zip)
119
120  maxsize = max([len(os.path.basename(i.filename))
121                 for i in input_tf_zip.infolist()
122                 if i.filename.endswith('.apk')])
123
124  # Check that all the APKs we want to sign have keys specified, and
125  # error out if they don't.  Do this before prompting for key
126  # passwords in case we're going to fail anyway.
127  unknown_apks = []
128  for info in input_tf_zip.infolist():
129    if info.filename.endswith(".apk"):
130      name = os.path.basename(info.filename)
131      if name not in apk_key_map:
132        unknown_apks.append(name)
133  if unknown_apks:
134    print "ERROR: no key specified for:\n\n ",
135    print "\n  ".join(unknown_apks)
136    print "\nUse '-e <apkname>=' to specify a key (which may be an"
137    print "empty string to not sign this apk)."
138    sys.exit(1)
139
140  key_passwords = common.GetKeyPasswords(set(apk_key_map.values()))
141
142  for info in input_tf_zip.infolist():
143    data = input_tf_zip.read(info.filename)
144    out_info = copy.copy(info)
145    if info.filename.endswith(".apk"):
146      name = os.path.basename(info.filename)
147      key = apk_key_map[name]
148      if key:
149        print "    signing: %-*s (%s)" % (maxsize, name, key)
150        signed_data = SignApk(data, key, key_passwords[key])
151        output_tf_zip.writestr(out_info, signed_data)
152      else:
153        # an APK we're not supposed to sign.
154        print "NOT signing: %s" % (name,)
155        output_tf_zip.writestr(out_info, data)
156    elif info.filename in ("SYSTEM/build.prop",
157                           "RECOVERY/RAMDISK/default.prop"):
158      print "rewriting %s:" % (info.filename,)
159      new_data = RewriteProps(data)
160      output_tf_zip.writestr(out_info, new_data)
161    else:
162      # a non-APK file; copy it verbatim
163      output_tf_zip.writestr(out_info, data)
164
165
166def RewriteProps(data):
167  output = []
168  for line in data.split("\n"):
169    line = line.strip()
170    original_line = line
171    if line and line[0] != '#':
172      key, value = line.split("=", 1)
173      if key == "ro.build.fingerprint":
174        pieces = line.split("/")
175        tags = set(pieces[-1].split(","))
176        if "test-keys" in tags:
177          tags.remove("test-keys")
178          tags.add("release-keys")
179          # TODO: from donut onwards, only add ota-rel-keys if -o is given.
180          tags.add("ota-rel-keys")
181        tags.update(OPTIONS.extra_tags)
182        line = "/".join(pieces[:-1] + [",".join(sorted(tags))])
183      elif key == "ro.build.description":
184        pieces = line.split(" ")
185        assert len(pieces) == 5
186        tags = set(pieces[-1].split(","))
187        if "test-keys" in tags:
188          tags.remove("test-keys")
189          tags.add("release-keys")
190          # TODO: from donut onwards, only add ota-rel-keys if -o is given.
191          tags.add("ota-rel-keys")
192        tags.update(OPTIONS.extra_tags)
193        line = " ".join(pieces[:-1] + [",".join(sorted(tags))])
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
201def 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  print "using:\n   ", "\n   ".join(mapped_keys)
216  print "for OTA package verification"
217
218  # recovery uses a version of the key that has been slightly
219  # predigested (by DumpPublicKey.java) and put in res/keys.
220
221  p = common.Run(["java", "-jar", OPTIONS.dumpkey_jar] + mapped_keys,
222                 stdout=subprocess.PIPE)
223  data, _ = p.communicate()
224  if p.returncode != 0:
225    raise ExternalError("failed to run dumpkeys")
226  output_tf_zip.writestr("RECOVERY/RAMDISK/res/keys", data)
227
228  # SystemUpdateActivity uses the x509.pem version of the keys, but
229  # put into a zipfile system/etc/security/otacerts.zip.
230
231  tempfile = cStringIO.StringIO()
232  certs_zip = zipfile.ZipFile(tempfile, "w")
233  for k in mapped_keys:
234    certs_zip.write(k)
235  certs_zip.close()
236  output_tf_zip.writestr("SYSTEM/etc/security/otacerts.zip",
237                         tempfile.getvalue())
238
239
240def main(argv):
241
242  def option_handler(o, a):
243    if o in ("-s", "--signapk_jar"):
244      OPTIONS.signapk_jar = a
245    elif o in ("-e", "--extra_apks"):
246      names, key = a.split("=")
247      names = names.split(",")
248      for n in names:
249        OPTIONS.extra_apks[n] = key
250    elif o in ("-d", "--default_key_mappings"):
251      OPTIONS.key_map.update({
252          "build/target/product/security/testkey": "%s/releasekey" % (a,),
253          "build/target/product/security/media": "%s/media" % (a,),
254          "build/target/product/security/shared": "%s/shared" % (a,),
255          "build/target/product/security/platform": "%s/platform" % (a,),
256          })
257    elif o in ("-k", "--key_mapping"):
258      s, d = a.split("=")
259      OPTIONS.key_map[s] = d
260    elif o in ("-o", "--replace_ota_keys"):
261      OPTIONS.replace_ota_keys = True
262    elif o in ("-t", "--extra_tags"):
263      OPTIONS.extra_tags.append(a)
264    else:
265      return False
266    return True
267
268  args = common.ParseOptions(argv, __doc__,
269                             extra_opts="s:e:d:k:ot:",
270                             extra_long_opts=["signapk_jar=",
271                                              "extra_apks=",
272                                              "default_key_mappings=",
273                                              "key_mapping=",
274                                              "replace_ota_keys",
275                                              "extra_tag="],
276                             extra_option_handler=option_handler)
277
278  if len(args) != 2:
279    common.Usage(__doc__)
280    sys.exit(1)
281
282  input_zip = zipfile.ZipFile(args[0], "r")
283  output_zip = zipfile.ZipFile(args[1], "w")
284
285  SignApks(input_zip, output_zip)
286
287  if OPTIONS.replace_ota_keys:
288    ReplaceOtaKeys(input_zip, output_zip)
289
290  input_zip.close()
291  output_zip.close()
292
293  print "done."
294
295
296if __name__ == '__main__':
297  try:
298    main(sys.argv[1:])
299  except common.ExternalError, e:
300    print
301    print "   ERROR: %s" % (e,)
302    print
303    sys.exit(1)
304