OSDN Git Service

eclair snapshot
[android-x86/build.git] / tools / releasetools / sign_target_files_apks
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,+ota-rel-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", "+ota-rel-keys", "+release-keys")
84
85 def GetApkCerts(tf_zip):
86   certmap = {}
87   for line in tf_zip.read("META/apkcerts.txt").split("\n"):
88     line = line.strip()
89     if not line: continue
90     m = re.match(r'^name="(.*)"\s+certificate="(.*)\.x509\.pem"\s+'
91                  r'private_key="\2\.pk8"$', line)
92     if not m:
93       raise SigningError("failed to parse line from apkcerts.txt:\n" + line)
94     certmap[m.group(1)] = OPTIONS.key_map.get(m.group(2), m.group(2))
95   for apk, cert in OPTIONS.extra_apks.iteritems():
96     certmap[apk] = OPTIONS.key_map.get(cert, cert)
97   return certmap
98
99
100 def CheckAllApksSigned(input_tf_zip, apk_key_map):
101   """Check that all the APKs we want to sign have keys specified, and
102   error out if they don't."""
103   unknown_apks = []
104   for info in input_tf_zip.infolist():
105     if info.filename.endswith(".apk"):
106       name = os.path.basename(info.filename)
107       if name not in apk_key_map:
108         unknown_apks.append(name)
109   if unknown_apks:
110     print "ERROR: no key specified for:\n\n ",
111     print "\n  ".join(unknown_apks)
112     print "\nUse '-e <apkname>=' to specify a key (which may be an"
113     print "empty string to not sign this apk)."
114     sys.exit(1)
115
116
117 def SharedUserForApk(data):
118   tmp = tempfile.NamedTemporaryFile()
119   tmp.write(data)
120   tmp.flush()
121
122   p = common.Run(["aapt", "dump", "xmltree", tmp.name, "AndroidManifest.xml"],
123                  stdout=subprocess.PIPE)
124   data, _ = p.communicate()
125   if p.returncode != 0:
126     raise ExternalError("failed to run aapt dump")
127   lines = data.split("\n")
128   for i in lines:
129     m = re.match(r'^\s*A: android:sharedUserId\([0-9a-fx]*\)="([^"]*)" .*$', i)
130     if m:
131       return m.group(1)
132   return None
133
134
135 def CheckSharedUserIdsConsistent(input_tf_zip, apk_key_map):
136   """Check that all packages that request the same shared user id are
137   going to be signed with the same key."""
138
139   shared_user_apks = {}
140   maxlen = len("(unknown key)")
141
142   for info in input_tf_zip.infolist():
143     if info.filename.endswith(".apk"):
144       data = input_tf_zip.read(info.filename)
145
146       name = os.path.basename(info.filename)
147       shared_user = SharedUserForApk(data)
148       key = apk_key_map[name]
149       maxlen = max(maxlen, len(key))
150
151       if shared_user is not None:
152         shared_user_apks.setdefault(
153             shared_user, {}).setdefault(key, []).append(name)
154
155   errors = []
156   for k, v in shared_user_apks.iteritems():
157     # each shared user should have exactly one key used for all the
158     # apks that want that user.
159     if len(v) > 1:
160       errors.append((k, v))
161
162   if not errors: return
163
164   print "ERROR:  shared user inconsistency.  All apks wanting to use"
165   print "        a given shared user must be signed with the same key."
166   print
167   errors.sort()
168   for user, keys in errors:
169     print 'shared user id "%s":' % (user,)
170     for key, apps in keys.iteritems():
171       print '  %-*s   %s' % (maxlen, key or "(unknown key)", apps[0])
172       for a in apps[1:]:
173         print (' ' * (maxlen+5)) + a
174     print
175
176   sys.exit(1)
177
178
179 def SignApk(data, keyname, pw):
180   unsigned = tempfile.NamedTemporaryFile()
181   unsigned.write(data)
182   unsigned.flush()
183
184   signed = tempfile.NamedTemporaryFile()
185
186   common.SignFile(unsigned.name, signed.name, keyname, pw, align=4)
187
188   data = signed.read()
189   unsigned.close()
190   signed.close()
191
192   return data
193
194
195 def SignApks(input_tf_zip, output_tf_zip, apk_key_map, key_passwords):
196   maxsize = max([len(os.path.basename(i.filename))
197                  for i in input_tf_zip.infolist()
198                  if i.filename.endswith('.apk')])
199
200   for info in input_tf_zip.infolist():
201     data = input_tf_zip.read(info.filename)
202     out_info = copy.copy(info)
203     if info.filename.endswith(".apk"):
204       name = os.path.basename(info.filename)
205       key = apk_key_map[name]
206       if key:
207         print "    signing: %-*s (%s)" % (maxsize, name, key)
208         signed_data = SignApk(data, key, key_passwords[key])
209         output_tf_zip.writestr(out_info, signed_data)
210       else:
211         # an APK we're not supposed to sign.
212         print "NOT signing: %s" % (name,)
213         output_tf_zip.writestr(out_info, data)
214     elif info.filename in ("SYSTEM/build.prop",
215                            "RECOVERY/RAMDISK/default.prop"):
216       print "rewriting %s:" % (info.filename,)
217       new_data = RewriteProps(data)
218       output_tf_zip.writestr(out_info, new_data)
219     else:
220       # a non-APK file; copy it verbatim
221       output_tf_zip.writestr(out_info, data)
222
223
224 def RewriteProps(data):
225   output = []
226   for line in data.split("\n"):
227     line = line.strip()
228     original_line = line
229     if line and line[0] != '#':
230       key, value = line.split("=", 1)
231       if key == "ro.build.fingerprint":
232         pieces = line.split("/")
233         tags = set(pieces[-1].split(","))
234         for ch in OPTIONS.tag_changes:
235           if ch[0] == "-":
236             tags.discard(ch[1:])
237           elif ch[0] == "+":
238             tags.add(ch[1:])
239         line = "/".join(pieces[:-1] + [",".join(sorted(tags))])
240       elif key == "ro.build.description":
241         pieces = line.split(" ")
242         assert len(pieces) == 5
243         tags = set(pieces[-1].split(","))
244         for ch in OPTIONS.tag_changes:
245           if ch[0] == "-":
246             tags.discard(ch[1:])
247           elif ch[0] == "+":
248             tags.add(ch[1:])
249         line = " ".join(pieces[:-1] + [",".join(sorted(tags))])
250     if line != original_line:
251       print "  replace: ", original_line
252       print "     with: ", line
253     output.append(line)
254   return "\n".join(output) + "\n"
255
256
257 def ReplaceOtaKeys(input_tf_zip, output_tf_zip):
258   try:
259     keylist = input_tf_zip.read("META/otakeys.txt").split()
260   except KeyError:
261     raise ExternalError("can't read META/otakeys.txt from input")
262
263   mapped_keys = []
264   for k in keylist:
265     m = re.match(r"^(.*)\.x509\.pem$", k)
266     if not m:
267       raise ExternalError("can't parse \"%s\" from META/otakeys.txt" % (k,))
268     k = m.group(1)
269     mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem")
270
271   if mapped_keys:
272     print "using:\n   ", "\n   ".join(mapped_keys)
273     print "for OTA package verification"
274   else:
275     mapped_keys.append(
276         OPTIONS.key_map["build/target/product/security/testkey"] + ".x509.pem")
277     print "META/otakeys.txt has no keys; using", mapped_keys[0]
278
279   # recovery uses a version of the key that has been slightly
280   # predigested (by DumpPublicKey.java) and put in res/keys.
281
282   p = common.Run(["java", "-jar",
283                   os.path.join(OPTIONS.search_path, "framework", "dumpkey.jar")]
284                  + mapped_keys,
285                  stdout=subprocess.PIPE)
286   data, _ = p.communicate()
287   if p.returncode != 0:
288     raise ExternalError("failed to run dumpkeys")
289   common.ZipWriteStr(output_tf_zip, "RECOVERY/RAMDISK/res/keys", data)
290
291   # SystemUpdateActivity uses the x509.pem version of the keys, but
292   # put into a zipfile system/etc/security/otacerts.zip.
293
294   tempfile = cStringIO.StringIO()
295   certs_zip = zipfile.ZipFile(tempfile, "w")
296   for k in mapped_keys:
297     certs_zip.write(k)
298   certs_zip.close()
299   common.ZipWriteStr(output_tf_zip, "SYSTEM/etc/security/otacerts.zip",
300                      tempfile.getvalue())
301
302
303 def main(argv):
304
305   def option_handler(o, a):
306     if o in ("-e", "--extra_apks"):
307       names, key = a.split("=")
308       names = names.split(",")
309       for n in names:
310         OPTIONS.extra_apks[n] = key
311     elif o in ("-d", "--default_key_mappings"):
312       OPTIONS.key_map.update({
313           "build/target/product/security/testkey": "%s/releasekey" % (a,),
314           "build/target/product/security/media": "%s/media" % (a,),
315           "build/target/product/security/shared": "%s/shared" % (a,),
316           "build/target/product/security/platform": "%s/platform" % (a,),
317           })
318     elif o in ("-k", "--key_mapping"):
319       s, d = a.split("=")
320       OPTIONS.key_map[s] = d
321     elif o in ("-o", "--replace_ota_keys"):
322       OPTIONS.replace_ota_keys = True
323     elif o in ("-t", "--tag_changes"):
324       new = []
325       for i in a.split(","):
326         i = i.strip()
327         if not i or i[0] not in "-+":
328           raise ValueError("Bad tag change '%s'" % (i,))
329         new.append(i[0] + i[1:].strip())
330       OPTIONS.tag_changes = tuple(new)
331     else:
332       return False
333     return True
334
335   args = common.ParseOptions(argv, __doc__,
336                              extra_opts="e:d:k:ot:",
337                              extra_long_opts=["extra_apks=",
338                                               "default_key_mappings=",
339                                               "key_mapping=",
340                                               "replace_ota_keys",
341                                               "tag_changes="],
342                              extra_option_handler=option_handler)
343
344   if len(args) != 2:
345     common.Usage(__doc__)
346     sys.exit(1)
347
348   input_zip = zipfile.ZipFile(args[0], "r")
349   output_zip = zipfile.ZipFile(args[1], "w")
350
351   apk_key_map = GetApkCerts(input_zip)
352   CheckAllApksSigned(input_zip, apk_key_map)
353   CheckSharedUserIdsConsistent(input_zip, apk_key_map)
354
355   key_passwords = common.GetKeyPasswords(set(apk_key_map.values()))
356   SignApks(input_zip, output_zip, apk_key_map, key_passwords)
357
358   if OPTIONS.replace_ota_keys:
359     ReplaceOtaKeys(input_zip, output_zip)
360
361   input_zip.close()
362   output_zip.close()
363
364   print "done."
365
366
367 if __name__ == '__main__':
368   try:
369     main(sys.argv[1:])
370   except common.ExternalError, e:
371     print
372     print "   ERROR: %s" % (e,)
373     print
374     sys.exit(1)