OSDN Git Service

am 6ef046c8: Fixes ProGuard options.
[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 = {}
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 SignApk(data, keyname, pw):
118   unsigned = tempfile.NamedTemporaryFile()
119   unsigned.write(data)
120   unsigned.flush()
121
122   signed = tempfile.NamedTemporaryFile()
123
124   common.SignFile(unsigned.name, signed.name, keyname, pw, align=4)
125
126   data = signed.read()
127   unsigned.close()
128   signed.close()
129
130   return data
131
132
133 def SignApks(input_tf_zip, output_tf_zip, apk_key_map, key_passwords):
134   maxsize = max([len(os.path.basename(i.filename))
135                  for i in input_tf_zip.infolist()
136                  if i.filename.endswith('.apk')])
137
138   for info in input_tf_zip.infolist():
139     data = input_tf_zip.read(info.filename)
140     out_info = copy.copy(info)
141     if info.filename.endswith(".apk"):
142       name = os.path.basename(info.filename)
143       key = apk_key_map[name]
144       if key:
145         print "    signing: %-*s (%s)" % (maxsize, name, key)
146         signed_data = SignApk(data, key, key_passwords[key])
147         output_tf_zip.writestr(out_info, signed_data)
148       else:
149         # an APK we're not supposed to sign.
150         print "NOT signing: %s" % (name,)
151         output_tf_zip.writestr(out_info, data)
152     elif info.filename in ("SYSTEM/build.prop",
153                            "RECOVERY/RAMDISK/default.prop"):
154       print "rewriting %s:" % (info.filename,)
155       new_data = RewriteProps(data)
156       output_tf_zip.writestr(out_info, new_data)
157     else:
158       # a non-APK file; copy it verbatim
159       output_tf_zip.writestr(out_info, data)
160
161
162 def RewriteProps(data):
163   output = []
164   for line in data.split("\n"):
165     line = line.strip()
166     original_line = line
167     if line and line[0] != '#':
168       key, value = line.split("=", 1)
169       if key == "ro.build.fingerprint":
170         pieces = line.split("/")
171         tags = set(pieces[-1].split(","))
172         for ch in OPTIONS.tag_changes:
173           if ch[0] == "-":
174             tags.discard(ch[1:])
175           elif ch[0] == "+":
176             tags.add(ch[1:])
177         line = "/".join(pieces[:-1] + [",".join(sorted(tags))])
178       elif key == "ro.build.description":
179         pieces = line.split(" ")
180         assert len(pieces) == 5
181         tags = set(pieces[-1].split(","))
182         for ch in OPTIONS.tag_changes:
183           if ch[0] == "-":
184             tags.discard(ch[1:])
185           elif ch[0] == "+":
186             tags.add(ch[1:])
187         line = " ".join(pieces[:-1] + [",".join(sorted(tags))])
188     if line != original_line:
189       print "  replace: ", original_line
190       print "     with: ", line
191     output.append(line)
192   return "\n".join(output) + "\n"
193
194
195 def ReplaceOtaKeys(input_tf_zip, output_tf_zip):
196   try:
197     keylist = input_tf_zip.read("META/otakeys.txt").split()
198   except KeyError:
199     raise ExternalError("can't read META/otakeys.txt from input")
200
201   mapped_keys = []
202   for k in keylist:
203     m = re.match(r"^(.*)\.x509\.pem$", k)
204     if not m:
205       raise ExternalError("can't parse \"%s\" from META/otakeys.txt" % (k,))
206     k = m.group(1)
207     mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem")
208
209   if mapped_keys:
210     print "using:\n   ", "\n   ".join(mapped_keys)
211     print "for OTA package verification"
212   else:
213     mapped_keys.append(
214         OPTIONS.key_map["build/target/product/security/testkey"] + ".x509.pem")
215     print "META/otakeys.txt has no keys; using", mapped_keys[0]
216
217   # recovery uses a version of the key that has been slightly
218   # predigested (by DumpPublicKey.java) and put in res/keys.
219
220   p = common.Run(["java", "-jar",
221                   os.path.join(OPTIONS.search_path, "framework", "dumpkey.jar")]
222                  + mapped_keys,
223                  stdout=subprocess.PIPE)
224   data, _ = p.communicate()
225   if p.returncode != 0:
226     raise ExternalError("failed to run dumpkeys")
227   common.ZipWriteStr(output_tf_zip, "RECOVERY/RAMDISK/res/keys", data)
228
229   # SystemUpdateActivity uses the x509.pem version of the keys, but
230   # put into a zipfile system/etc/security/otacerts.zip.
231
232   tempfile = cStringIO.StringIO()
233   certs_zip = zipfile.ZipFile(tempfile, "w")
234   for k in mapped_keys:
235     certs_zip.write(k)
236   certs_zip.close()
237   common.ZipWriteStr(output_tf_zip, "SYSTEM/etc/security/otacerts.zip",
238                      tempfile.getvalue())
239
240
241 def main(argv):
242
243   def option_handler(o, a):
244     if o in ("-e", "--extra_apks"):
245       names, key = a.split("=")
246       names = names.split(",")
247       for n in names:
248         OPTIONS.extra_apks[n] = key
249     elif o in ("-d", "--default_key_mappings"):
250       OPTIONS.key_map.update({
251           "build/target/product/security/testkey": "%s/releasekey" % (a,),
252           "build/target/product/security/media": "%s/media" % (a,),
253           "build/target/product/security/shared": "%s/shared" % (a,),
254           "build/target/product/security/platform": "%s/platform" % (a,),
255           })
256     elif o in ("-k", "--key_mapping"):
257       s, d = a.split("=")
258       OPTIONS.key_map[s] = d
259     elif o in ("-o", "--replace_ota_keys"):
260       OPTIONS.replace_ota_keys = True
261     elif o in ("-t", "--tag_changes"):
262       new = []
263       for i in a.split(","):
264         i = i.strip()
265         if not i or i[0] not in "-+":
266           raise ValueError("Bad tag change '%s'" % (i,))
267         new.append(i[0] + i[1:].strip())
268       OPTIONS.tag_changes = tuple(new)
269     else:
270       return False
271     return True
272
273   args = common.ParseOptions(argv, __doc__,
274                              extra_opts="e:d:k:ot:",
275                              extra_long_opts=["extra_apks=",
276                                               "default_key_mappings=",
277                                               "key_mapping=",
278                                               "replace_ota_keys",
279                                               "tag_changes="],
280                              extra_option_handler=option_handler)
281
282   if len(args) != 2:
283     common.Usage(__doc__)
284     sys.exit(1)
285
286   input_zip = zipfile.ZipFile(args[0], "r")
287   output_zip = zipfile.ZipFile(args[1], "w")
288
289   apk_key_map = GetApkCerts(input_zip)
290   CheckAllApksSigned(input_zip, apk_key_map)
291
292   key_passwords = common.GetKeyPasswords(set(apk_key_map.values()))
293   SignApks(input_zip, output_zip, apk_key_map, key_passwords)
294
295   if OPTIONS.replace_ota_keys:
296     ReplaceOtaKeys(input_zip, output_zip)
297
298   input_zip.close()
299   output_zip.close()
300
301   print "done."
302
303
304 if __name__ == '__main__':
305   try:
306     main(sys.argv[1:])
307   except common.ExternalError, e:
308     print
309     print "   ERROR: %s" % (e,)
310     print
311     sys.exit(1)