OSDN Git Service

map -e with no cert to PRESIGNED
[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 RewriteProps(data):
164   output = []
165   for line in data.split("\n"):
166     line = line.strip()
167     original_line = line
168     if line and line[0] != '#':
169       key, value = line.split("=", 1)
170       if key == "ro.build.fingerprint":
171         pieces = line.split("/")
172         tags = set(pieces[-1].split(","))
173         for ch in OPTIONS.tag_changes:
174           if ch[0] == "-":
175             tags.discard(ch[1:])
176           elif ch[0] == "+":
177             tags.add(ch[1:])
178         line = "/".join(pieces[:-1] + [",".join(sorted(tags))])
179       elif key == "ro.build.description":
180         pieces = line.split(" ")
181         assert len(pieces) == 5
182         tags = set(pieces[-1].split(","))
183         for ch in OPTIONS.tag_changes:
184           if ch[0] == "-":
185             tags.discard(ch[1:])
186           elif ch[0] == "+":
187             tags.add(ch[1:])
188         line = " ".join(pieces[:-1] + [",".join(sorted(tags))])
189     if line != original_line:
190       print "  replace: ", original_line
191       print "     with: ", line
192     output.append(line)
193   return "\n".join(output) + "\n"
194
195
196 def ReplaceOtaKeys(input_tf_zip, output_tf_zip):
197   try:
198     keylist = input_tf_zip.read("META/otakeys.txt").split()
199   except KeyError:
200     raise ExternalError("can't read META/otakeys.txt from input")
201
202   mapped_keys = []
203   for k in keylist:
204     m = re.match(r"^(.*)\.x509\.pem$", k)
205     if not m:
206       raise ExternalError("can't parse \"%s\" from META/otakeys.txt" % (k,))
207     k = m.group(1)
208     mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem")
209
210   if mapped_keys:
211     print "using:\n   ", "\n   ".join(mapped_keys)
212     print "for OTA package verification"
213   else:
214     mapped_keys.append(
215         OPTIONS.key_map["build/target/product/security/testkey"] + ".x509.pem")
216     print "META/otakeys.txt has no keys; using", mapped_keys[0]
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",
222                   os.path.join(OPTIONS.search_path, "framework", "dumpkey.jar")]
223                  + mapped_keys,
224                  stdout=subprocess.PIPE)
225   data, _ = p.communicate()
226   if p.returncode != 0:
227     raise ExternalError("failed to run dumpkeys")
228   common.ZipWriteStr(output_tf_zip, "RECOVERY/RAMDISK/res/keys", data)
229
230   # SystemUpdateActivity uses the x509.pem version of the keys, but
231   # put into a zipfile system/etc/security/otacerts.zip.
232
233   tempfile = cStringIO.StringIO()
234   certs_zip = zipfile.ZipFile(tempfile, "w")
235   for k in mapped_keys:
236     certs_zip.write(k)
237   certs_zip.close()
238   common.ZipWriteStr(output_tf_zip, "SYSTEM/etc/security/otacerts.zip",
239                      tempfile.getvalue())
240
241
242 def main(argv):
243
244   def option_handler(o, a):
245     if 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", "--tag_changes"):
263       new = []
264       for i in a.split(","):
265         i = i.strip()
266         if not i or i[0] not in "-+":
267           raise ValueError("Bad tag change '%s'" % (i,))
268         new.append(i[0] + i[1:].strip())
269       OPTIONS.tag_changes = tuple(new)
270     else:
271       return False
272     return True
273
274   args = common.ParseOptions(argv, __doc__,
275                              extra_opts="e:d:k:ot:",
276                              extra_long_opts=["extra_apks=",
277                                               "default_key_mappings=",
278                                               "key_mapping=",
279                                               "replace_ota_keys",
280                                               "tag_changes="],
281                              extra_option_handler=option_handler)
282
283   if len(args) != 2:
284     common.Usage(__doc__)
285     sys.exit(1)
286
287   input_zip = zipfile.ZipFile(args[0], "r")
288   output_zip = zipfile.ZipFile(args[1], "w")
289
290   apk_key_map = GetApkCerts(input_zip)
291   CheckAllApksSigned(input_zip, apk_key_map)
292
293   key_passwords = common.GetKeyPasswords(set(apk_key_map.values()))
294   SignApks(input_zip, output_zip, apk_key_map, key_passwords)
295
296   if OPTIONS.replace_ota_keys:
297     ReplaceOtaKeys(input_zip, output_zip)
298
299   input_zip.close()
300   output_zip.close()
301
302   print "done."
303
304
305 if __name__ == '__main__':
306   try:
307     main(sys.argv[1:])
308   except common.ExternalError, e:
309     print
310     print "   ERROR: %s" % (e,)
311     print
312     sys.exit(1)