OSDN Git Service

03610b2f076e3f998be0f26afcd0e6abf30601cd
[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 m:
93       certmap[m.group(1)] = OPTIONS.key_map.get(m.group(2), m.group(2))
94     else:
95       m = re.match(r'^name="(.*)"\s+certificate="PRESIGNED"\s+'
96                  r'private_key=""$', line)
97       if m:
98         certmap[m.group(1)] = None
99       else:
100         raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
101   for apk, cert in OPTIONS.extra_apks.iteritems():
102     certmap[apk] = OPTIONS.key_map.get(cert, cert)
103   return certmap
104
105
106 def CheckAllApksSigned(input_tf_zip, apk_key_map):
107   """Check that all the APKs we want to sign have keys specified, and
108   error out if they don't."""
109   unknown_apks = []
110   for info in input_tf_zip.infolist():
111     if info.filename.endswith(".apk"):
112       name = os.path.basename(info.filename)
113       if name not in apk_key_map:
114         unknown_apks.append(name)
115   if unknown_apks:
116     print "ERROR: no key specified for:\n\n ",
117     print "\n  ".join(unknown_apks)
118     print "\nUse '-e <apkname>=' to specify a key (which may be an"
119     print "empty string to not sign this apk)."
120     sys.exit(1)
121
122
123 def SignApk(data, keyname, pw):
124   unsigned = tempfile.NamedTemporaryFile()
125   unsigned.write(data)
126   unsigned.flush()
127
128   signed = tempfile.NamedTemporaryFile()
129
130   common.SignFile(unsigned.name, signed.name, keyname, pw, align=4)
131
132   data = signed.read()
133   unsigned.close()
134   signed.close()
135
136   return data
137
138
139 def SignApks(input_tf_zip, output_tf_zip, apk_key_map, key_passwords):
140   maxsize = max([len(os.path.basename(i.filename))
141                  for i in input_tf_zip.infolist()
142                  if i.filename.endswith('.apk')])
143
144   for info in input_tf_zip.infolist():
145     data = input_tf_zip.read(info.filename)
146     out_info = copy.copy(info)
147     if info.filename.endswith(".apk"):
148       name = os.path.basename(info.filename)
149       key = apk_key_map[name]
150       if key:
151         print "    signing: %-*s (%s)" % (maxsize, name, key)
152         signed_data = SignApk(data, key, key_passwords[key])
153         output_tf_zip.writestr(out_info, signed_data)
154       else:
155         # an APK we're not supposed to sign.
156         print "NOT signing: %s" % (name,)
157         output_tf_zip.writestr(out_info, data)
158     elif info.filename in ("SYSTEM/build.prop",
159                            "RECOVERY/RAMDISK/default.prop"):
160       print "rewriting %s:" % (info.filename,)
161       new_data = RewriteProps(data)
162       output_tf_zip.writestr(out_info, new_data)
163     else:
164       # a non-APK file; copy it verbatim
165       output_tf_zip.writestr(out_info, data)
166
167
168 def RewriteProps(data):
169   output = []
170   for line in data.split("\n"):
171     line = line.strip()
172     original_line = line
173     if line and line[0] != '#':
174       key, value = line.split("=", 1)
175       if key == "ro.build.fingerprint":
176         pieces = line.split("/")
177         tags = set(pieces[-1].split(","))
178         for ch in OPTIONS.tag_changes:
179           if ch[0] == "-":
180             tags.discard(ch[1:])
181           elif ch[0] == "+":
182             tags.add(ch[1:])
183         line = "/".join(pieces[:-1] + [",".join(sorted(tags))])
184       elif key == "ro.build.description":
185         pieces = line.split(" ")
186         assert len(pieces) == 5
187         tags = set(pieces[-1].split(","))
188         for ch in OPTIONS.tag_changes:
189           if ch[0] == "-":
190             tags.discard(ch[1:])
191           elif ch[0] == "+":
192             tags.add(ch[1:])
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
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)