OSDN Git Service

add option to modify build fingerprint tags when signing
[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  (--extra_tag)  <tag>
58       A string which is added to the set of tags in the last component
59       of the build fingerprint.  Option may be repeated to give
60       multiple extra tags.
61 """
62
63 import sys
64
65 if sys.hexversion < 0x02040000:
66   print >> sys.stderr, "Python 2.4 or newer is required."
67   sys.exit(1)
68
69 import cStringIO
70 import copy
71 import os
72 import re
73 import subprocess
74 import tempfile
75 import zipfile
76
77 import common
78
79 OPTIONS = common.OPTIONS
80
81 OPTIONS.extra_apks = {}
82 OPTIONS.key_map = {}
83 OPTIONS.replace_ota_keys = False
84 OPTIONS.extra_tags = []
85
86 def GetApkCerts(tf_zip):
87   certmap = {}
88   for line in tf_zip.read("META/apkcerts.txt").split("\n"):
89     line = line.strip()
90     if not line: continue
91     m = re.match(r'^name="(.*)"\s+certificate="(.*)\.x509\.pem"\s+'
92                  r'private_key="\2\.pk8"$', line)
93     if not m:
94       raise SigningError("failed to parse line from apkcerts.txt:\n" + line)
95     certmap[m.group(1)] = OPTIONS.key_map.get(m.group(2), m.group(2))
96   for apk, cert in OPTIONS.extra_apks.iteritems():
97     certmap[apk] = OPTIONS.key_map.get(cert, cert)
98   return certmap
99
100
101 def SignApk(data, keyname, pw):
102   unsigned = tempfile.NamedTemporaryFile()
103   unsigned.write(data)
104   unsigned.flush()
105
106   signed = tempfile.NamedTemporaryFile()
107
108   common.SignFile(unsigned.name, signed.name, keyname, pw, align=4)
109
110   data = signed.read()
111   unsigned.close()
112   signed.close()
113
114   return data
115
116
117 def SignApks(input_tf_zip, output_tf_zip):
118   apk_key_map = GetApkCerts(input_tf_zip)
119
120   maxsize = max([len(os.path.basename(i.filename))
121                  for i in input_tf_zip.infolist()
122                  if i.filename.endswith('.apk')])
123
124   # Check that all the APKs we want to sign have keys specified, and
125   # error out if they don't.  Do this before prompting for key
126   # passwords in case we're going to fail anyway.
127   unknown_apks = []
128   for info in input_tf_zip.infolist():
129     if info.filename.endswith(".apk"):
130       name = os.path.basename(info.filename)
131       if name not in apk_key_map:
132         unknown_apks.append(name)
133   if unknown_apks:
134     print "ERROR: no key specified for:\n\n ",
135     print "\n  ".join(unknown_apks)
136     print "\nUse '-e <apkname>=' to specify a key (which may be an"
137     print "empty string to not sign this apk)."
138     sys.exit(1)
139
140   key_passwords = common.GetKeyPasswords(set(apk_key_map.values()))
141
142   for info in input_tf_zip.infolist():
143     data = input_tf_zip.read(info.filename)
144     out_info = copy.copy(info)
145     if info.filename.endswith(".apk"):
146       name = os.path.basename(info.filename)
147       key = apk_key_map[name]
148       if key:
149         print "    signing: %-*s (%s)" % (maxsize, name, key)
150         signed_data = SignApk(data, key, key_passwords[key])
151         output_tf_zip.writestr(out_info, signed_data)
152       else:
153         # an APK we're not supposed to sign.
154         print "NOT signing: %s" % (name,)
155         output_tf_zip.writestr(out_info, data)
156     elif info.filename in ("SYSTEM/build.prop",
157                            "RECOVERY/RAMDISK/default.prop"):
158       print "rewriting %s:" % (info.filename,)
159       new_data = RewriteProps(data)
160       output_tf_zip.writestr(out_info, new_data)
161     else:
162       # a non-APK file; copy it verbatim
163       output_tf_zip.writestr(out_info, data)
164
165
166 def RewriteProps(data):
167   output = []
168   for line in data.split("\n"):
169     line = line.strip()
170     original_line = line
171     if line and line[0] != '#':
172       key, value = line.split("=", 1)
173       if key == "ro.build.fingerprint":
174         pieces = line.split("/")
175         tags = set(pieces[-1].split(","))
176         if "test-keys" in tags:
177           tags.remove("test-keys")
178           tags.add("release-keys")
179           # TODO: from donut onwards, only add ota-rel-keys if -o is given.
180           tags.add("ota-rel-keys")
181         tags.update(OPTIONS.extra_tags)
182         line = "/".join(pieces[:-1] + [",".join(sorted(tags))])
183       elif key == "ro.build.description":
184         pieces = line.split(" ")
185         assert len(pieces) == 5
186         tags = set(pieces[-1].split(","))
187         if "test-keys" in tags:
188           tags.remove("test-keys")
189           tags.add("release-keys")
190           # TODO: from donut onwards, only add ota-rel-keys if -o is given.
191           tags.add("ota-rel-keys")
192         tags.update(OPTIONS.extra_tags)
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   print "using:\n   ", "\n   ".join(mapped_keys)
216   print "for OTA package verification"
217
218   # recovery uses a version of the key that has been slightly
219   # predigested (by DumpPublicKey.java) and put in res/keys.
220
221   p = common.Run(["java", "-jar", OPTIONS.dumpkey_jar] + mapped_keys,
222                  stdout=subprocess.PIPE)
223   data, _ = p.communicate()
224   if p.returncode != 0:
225     raise ExternalError("failed to run dumpkeys")
226   output_tf_zip.writestr("RECOVERY/RAMDISK/res/keys", data)
227
228   # SystemUpdateActivity uses the x509.pem version of the keys, but
229   # put into a zipfile system/etc/security/otacerts.zip.
230
231   tempfile = cStringIO.StringIO()
232   certs_zip = zipfile.ZipFile(tempfile, "w")
233   for k in mapped_keys:
234     certs_zip.write(k)
235   certs_zip.close()
236   output_tf_zip.writestr("SYSTEM/etc/security/otacerts.zip",
237                          tempfile.getvalue())
238
239
240 def main(argv):
241
242   def option_handler(o, a):
243     if o in ("-s", "--signapk_jar"):
244       OPTIONS.signapk_jar = a
245     elif o in ("-e", "--extra_apks"):
246       names, key = a.split("=")
247       names = names.split(",")
248       for n in names:
249         OPTIONS.extra_apks[n] = key
250     elif o in ("-d", "--default_key_mappings"):
251       OPTIONS.key_map.update({
252           "build/target/product/security/testkey": "%s/releasekey" % (a,),
253           "build/target/product/security/media": "%s/media" % (a,),
254           "build/target/product/security/shared": "%s/shared" % (a,),
255           "build/target/product/security/platform": "%s/platform" % (a,),
256           })
257     elif o in ("-k", "--key_mapping"):
258       s, d = a.split("=")
259       OPTIONS.key_map[s] = d
260     elif o in ("-o", "--replace_ota_keys"):
261       OPTIONS.replace_ota_keys = True
262     elif o in ("-t", "--extra_tags"):
263       OPTIONS.extra_tags.append(a)
264     else:
265       return False
266     return True
267
268   args = common.ParseOptions(argv, __doc__,
269                              extra_opts="s:e:d:k:ot:",
270                              extra_long_opts=["signapk_jar=",
271                                               "extra_apks=",
272                                               "default_key_mappings=",
273                                               "key_mapping=",
274                                               "replace_ota_keys",
275                                               "extra_tag="],
276                              extra_option_handler=option_handler)
277
278   if len(args) != 2:
279     common.Usage(__doc__)
280     sys.exit(1)
281
282   input_zip = zipfile.ZipFile(args[0], "r")
283   output_zip = zipfile.ZipFile(args[1], "w")
284
285   SignApks(input_zip, output_zip)
286
287   if OPTIONS.replace_ota_keys:
288     ReplaceOtaKeys(input_zip, output_zip)
289
290   input_zip.close()
291   output_zip.close()
292
293   print "done."
294
295
296 if __name__ == '__main__':
297   try:
298     main(sys.argv[1:])
299   except common.ExternalError, e:
300     print
301     print "   ERROR: %s" % (e,)
302     print
303     sys.exit(1)