OSDN Git Service

am ffae6f51: (-s ours) am 25a331ab: Merge "Update version string." into eclair
[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,+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)