OSDN Git Service

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