OSDN Git Service

1a3f074f3d351d47e9fb1322197fa859a134cd6f
[android-x86/build.git] / tools / releasetools / sign_target_files_apks.py
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         $devkey/devkey    ==>  $dir/releasekey
40         $devkey/testkey   ==>  $dir/releasekey
41         $devkey/media     ==>  $dir/media
42         $devkey/shared    ==>  $dir/shared
43         $devkey/platform  ==>  $dir/platform
44
45       where $devkey is the directory part of the value of
46       default_system_dev_certificate from the input target-files's
47       META/misc_info.txt.  (Defaulting to "build/target/product/security"
48       if the value is not present in misc_info.
49
50       -d and -k options are added to the set of mappings in the order
51       in which they appear on the command line.
52
53   -o  (--replace_ota_keys)
54       Replace the certificate (public key) used by OTA package verification
55       with the ones specified in the input target_files zip (in the
56       META/otakeys.txt file). Key remapping (-k and -d) is performed on the
57       keys. For A/B devices, the payload verification key will be replaced
58       as well. If there're multiple OTA keys, only the first one will be used
59       for payload verification.
60
61   -t  (--tag_changes)  <+tag>,<-tag>,...
62       Comma-separated list of changes to make to the set of tags (in
63       the last component of the build fingerprint).  Prefix each with
64       '+' or '-' to indicate whether that tag should be added or
65       removed.  Changes are processed in the order they appear.
66       Default value is "-test-keys,-dev-keys,+release-keys".
67
68   --replace_verity_keyid <path_to_X509_PEM_cert_file>
69       Replace the veritykeyid in BOOT/cmdline of input_target_file_zip
70       with keyid of the cert pointed by <path_to_X509_PEM_cert_file>
71 """
72
73 import sys
74
75 if sys.hexversion < 0x02070000:
76   print >> sys.stderr, "Python 2.7 or newer is required."
77   sys.exit(1)
78
79 import base64
80 import cStringIO
81 import copy
82 import errno
83 import os
84 import re
85 import shutil
86 import subprocess
87 import tempfile
88 import zipfile
89
90 import add_img_to_target_files
91 import common
92
93 OPTIONS = common.OPTIONS
94
95 OPTIONS.extra_apks = {}
96 OPTIONS.key_map = {}
97 OPTIONS.replace_ota_keys = False
98 OPTIONS.replace_verity_public_key = False
99 OPTIONS.replace_verity_private_key = False
100 OPTIONS.replace_verity_keyid = False
101 OPTIONS.tag_changes = ("-test-keys", "-dev-keys", "+release-keys")
102
103 def GetApkCerts(tf_zip):
104   certmap = common.ReadApkCerts(tf_zip)
105
106   # apply the key remapping to the contents of the file
107   for apk, cert in certmap.iteritems():
108     certmap[apk] = OPTIONS.key_map.get(cert, cert)
109
110   # apply all the -e options, overriding anything in the file
111   for apk, cert in OPTIONS.extra_apks.iteritems():
112     if not cert:
113       cert = "PRESIGNED"
114     certmap[apk] = OPTIONS.key_map.get(cert, cert)
115
116   return certmap
117
118
119 def CheckAllApksSigned(input_tf_zip, apk_key_map):
120   """Check that all the APKs we want to sign have keys specified, and
121   error out if they don't."""
122   unknown_apks = []
123   for info in input_tf_zip.infolist():
124     if info.filename.endswith(".apk"):
125       name = os.path.basename(info.filename)
126       if name not in apk_key_map:
127         unknown_apks.append(name)
128   if unknown_apks:
129     print "ERROR: no key specified for:\n\n ",
130     print "\n  ".join(unknown_apks)
131     print "\nUse '-e <apkname>=' to specify a key (which may be an"
132     print "empty string to not sign this apk)."
133     sys.exit(1)
134
135
136 def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map):
137   unsigned = tempfile.NamedTemporaryFile()
138   unsigned.write(data)
139   unsigned.flush()
140
141   signed = tempfile.NamedTemporaryFile()
142
143   # For pre-N builds, don't upgrade to SHA-256 JAR signatures based on the APK's
144   # minSdkVersion to avoid increasing incremental OTA update sizes. If an APK
145   # didn't change, we don't want its signature to change due to the switch
146   # from SHA-1 to SHA-256.
147   # By default, APK signer chooses SHA-256 signatures if the APK's minSdkVersion
148   # is 18 or higher. For pre-N builds we disable this mechanism by pretending
149   # that the APK's minSdkVersion is 1.
150   # For N+ builds, we let APK signer rely on the APK's minSdkVersion to
151   # determine whether to use SHA-256.
152   min_api_level = None
153   if platform_api_level > 23:
154     # Let APK signer choose whether to use SHA-1 or SHA-256, based on the APK's
155     # minSdkVersion attribute
156     min_api_level = None
157   else:
158     # Force APK signer to use SHA-1
159     min_api_level = 1
160
161   common.SignFile(unsigned.name, signed.name, keyname, pw,
162       min_api_level=min_api_level,
163       codename_to_api_level_map=codename_to_api_level_map)
164
165   data = signed.read()
166   unsigned.close()
167   signed.close()
168
169   return data
170
171
172 def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
173                        apk_key_map, key_passwords, platform_api_level,
174                        codename_to_api_level_map):
175
176   maxsize = max([len(os.path.basename(i.filename))
177                  for i in input_tf_zip.infolist()
178                  if i.filename.endswith('.apk')])
179   rebuild_recovery = False
180   system_root_image = misc_info.get("system_root_image") == "true"
181
182   # tmpdir will only be used to regenerate the recovery-from-boot patch.
183   tmpdir = tempfile.mkdtemp()
184   def write_to_temp(fn, attr, data):
185     fn = os.path.join(tmpdir, fn)
186     if fn.endswith("/"):
187       fn = os.path.join(tmpdir, fn)
188       os.mkdir(fn)
189     else:
190       d = os.path.dirname(fn)
191       if d and not os.path.exists(d):
192         os.makedirs(d)
193
194       if attr >> 16 == 0xa1ff:
195         os.symlink(data, fn)
196       else:
197         with open(fn, "wb") as f:
198           f.write(data)
199
200   for info in input_tf_zip.infolist():
201     if info.filename.startswith("IMAGES/"):
202       continue
203
204     data = input_tf_zip.read(info.filename)
205     out_info = copy.copy(info)
206
207     # Replace keys if requested.
208     if (info.filename == "META/misc_info.txt" and
209         OPTIONS.replace_verity_private_key):
210       ReplaceVerityPrivateKey(input_tf_zip, output_tf_zip, misc_info,
211                               OPTIONS.replace_verity_private_key[1])
212     elif (info.filename in ("BOOT/RAMDISK/verity_key",
213                             "BOOT/verity_key") and
214           OPTIONS.replace_verity_public_key):
215       new_data = ReplaceVerityPublicKey(output_tf_zip, info.filename,
216                                         OPTIONS.replace_verity_public_key[1])
217       write_to_temp(info.filename, info.external_attr, new_data)
218     elif (info.filename == "BOOT/cmdline" and
219           OPTIONS.replace_verity_keyid):
220       new_cmdline = ReplaceVerityKeyId(input_tf_zip, output_tf_zip,
221           OPTIONS.replace_verity_keyid[1])
222       # Writing the new cmdline to tmpdir is redundant as the bootimage
223       # gets build in the add_image_to_target_files and rebuild_recovery
224       # is not exercised while building the boot image for the A/B
225       # path
226       write_to_temp(info.filename, info.external_attr, new_cmdline)
227     # Sign APKs.
228     if info.filename.endswith(".apk"):
229       name = os.path.basename(info.filename)
230       key = apk_key_map[name]
231       if key not in common.SPECIAL_CERT_STRINGS:
232         print "    signing: %-*s (%s)" % (maxsize, name, key)
233         signed_data = SignApk(data, key, key_passwords[key], platform_api_level,
234             codename_to_api_level_map)
235         common.ZipWriteStr(output_tf_zip, out_info, signed_data)
236       else:
237         # an APK we're not supposed to sign.
238         print "NOT signing: %s" % (name,)
239         common.ZipWriteStr(output_tf_zip, out_info, data)
240
241     # System properties.
242     elif info.filename in ("SYSTEM/build.prop",
243                            "VENDOR/build.prop",
244                            "BOOT/RAMDISK/default.prop",
245                            "RECOVERY/RAMDISK/default.prop"):
246       print "rewriting %s:" % (info.filename,)
247       new_data = RewriteProps(data, misc_info)
248       common.ZipWriteStr(output_tf_zip, out_info, new_data)
249       if info.filename in ("BOOT/RAMDISK/default.prop",
250                            "RECOVERY/RAMDISK/default.prop"):
251         write_to_temp(info.filename, info.external_attr, new_data)
252
253     elif info.filename.endswith("mac_permissions.xml"):
254       print "rewriting %s with new keys." % (info.filename,)
255       new_data = ReplaceCerts(data)
256       common.ZipWriteStr(output_tf_zip, out_info, new_data)
257
258     # Trigger a rebuild of the recovery patch if needed.
259     elif info.filename in ("SYSTEM/recovery-from-boot.p",
260                            "SYSTEM/etc/recovery.img",
261                            "SYSTEM/bin/install-recovery.sh"):
262       rebuild_recovery = True
263
264     # Don't copy OTA keys if we're replacing them.
265     elif (OPTIONS.replace_ota_keys and
266           info.filename in (
267               "BOOT/RAMDISK/res/keys",
268               "RECOVERY/RAMDISK/res/keys",
269               "SYSTEM/etc/security/otacerts.zip",
270               "SYSTEM/etc/update_engine/update-payload-key.pub.pem")):
271       pass
272
273     # Skip verity keys since they have been processed above.
274     # TODO: verity_key is at a wrong location (BOOT/verity_key). Will fix and
275     # clean up verity related lines in a separate CL.
276     elif (OPTIONS.replace_verity_private_key and
277           info.filename == "META/misc_info.txt"):
278       pass
279     elif (OPTIONS.replace_verity_public_key and
280           info.filename in ("BOOT/RAMDISK/verity_key",
281                             "BOOT/verity_key")):
282       pass
283
284     elif (info.filename == "BOOT/cmdline" and
285           OPTIONS.replace_verity_keyid):
286       pass
287
288     # Copy BOOT/, RECOVERY/, META/, ROOT/ to rebuild recovery patch. This case
289     # must come AFTER other matching rules.
290     elif (info.filename.startswith("BOOT/") or
291           info.filename.startswith("RECOVERY/") or
292           info.filename.startswith("META/") or
293           info.filename.startswith("ROOT/") or
294           info.filename == "SYSTEM/etc/recovery-resource.dat"):
295       write_to_temp(info.filename, info.external_attr, data)
296       common.ZipWriteStr(output_tf_zip, out_info, data)
297
298     # A non-APK file; copy it verbatim.
299     else:
300       common.ZipWriteStr(output_tf_zip, out_info, data)
301
302   if OPTIONS.replace_ota_keys:
303     new_recovery_keys = ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info)
304     if new_recovery_keys:
305       if system_root_image:
306         recovery_keys_location = "BOOT/RAMDISK/res/keys"
307       else:
308         recovery_keys_location = "RECOVERY/RAMDISK/res/keys"
309       # The "new_recovery_keys" has been already written into the output_tf_zip
310       # while calling ReplaceOtaKeys(). We're just putting the same copy to
311       # tmpdir in case we need to regenerate the recovery-from-boot patch.
312       write_to_temp(recovery_keys_location, 0o755 << 16, new_recovery_keys)
313
314   if rebuild_recovery:
315     recovery_img = common.GetBootableImage(
316         "recovery.img", "recovery.img", tmpdir, "RECOVERY", info_dict=misc_info)
317     boot_img = common.GetBootableImage(
318         "boot.img", "boot.img", tmpdir, "BOOT", info_dict=misc_info)
319
320     def output_sink(fn, data):
321       common.ZipWriteStr(output_tf_zip, "SYSTEM/" + fn, data)
322
323     common.MakeRecoveryPatch(tmpdir, output_sink, recovery_img, boot_img,
324                              info_dict=misc_info)
325
326   shutil.rmtree(tmpdir)
327
328
329 def ReplaceCerts(data):
330   """Given a string of data, replace all occurences of a set
331   of X509 certs with a newer set of X509 certs and return
332   the updated data string."""
333   for old, new in OPTIONS.key_map.iteritems():
334     try:
335       if OPTIONS.verbose:
336         print "    Replacing %s.x509.pem with %s.x509.pem" % (old, new)
337       f = open(old + ".x509.pem")
338       old_cert16 = base64.b16encode(common.ParseCertificate(f.read())).lower()
339       f.close()
340       f = open(new + ".x509.pem")
341       new_cert16 = base64.b16encode(common.ParseCertificate(f.read())).lower()
342       f.close()
343       # Only match entire certs.
344       pattern = "\\b"+old_cert16+"\\b"
345       (data, num) = re.subn(pattern, new_cert16, data, flags=re.IGNORECASE)
346       if OPTIONS.verbose:
347         print "    Replaced %d occurence(s) of %s.x509.pem with " \
348             "%s.x509.pem" % (num, old, new)
349     except IOError as e:
350       if e.errno == errno.ENOENT and not OPTIONS.verbose:
351         continue
352
353       print "    Error accessing %s. %s. Skip replacing %s.x509.pem " \
354           "with %s.x509.pem." % (e.filename, e.strerror, old, new)
355
356   return data
357
358
359 def EditTags(tags):
360   """Given a string containing comma-separated tags, apply the edits
361   specified in OPTIONS.tag_changes and return the updated string."""
362   tags = set(tags.split(","))
363   for ch in OPTIONS.tag_changes:
364     if ch[0] == "-":
365       tags.discard(ch[1:])
366     elif ch[0] == "+":
367       tags.add(ch[1:])
368   return ",".join(sorted(tags))
369
370
371 def RewriteProps(data, misc_info):
372   output = []
373   for line in data.split("\n"):
374     line = line.strip()
375     original_line = line
376     if line and line[0] != '#' and "=" in line:
377       key, value = line.split("=", 1)
378       if (key in ("ro.build.fingerprint", "ro.vendor.build.fingerprint")
379           and misc_info.get("oem_fingerprint_properties") is None):
380         pieces = value.split("/")
381         pieces[-1] = EditTags(pieces[-1])
382         value = "/".join(pieces)
383       elif (key in ("ro.build.thumbprint", "ro.vendor.build.thumbprint")
384             and misc_info.get("oem_fingerprint_properties") is not None):
385         pieces = value.split("/")
386         pieces[-1] = EditTags(pieces[-1])
387         value = "/".join(pieces)
388       elif key == "ro.bootimage.build.fingerprint":
389         pieces = value.split("/")
390         pieces[-1] = EditTags(pieces[-1])
391         value = "/".join(pieces)
392       elif key == "ro.build.description":
393         pieces = value.split(" ")
394         assert len(pieces) == 5
395         pieces[-1] = EditTags(pieces[-1])
396         value = " ".join(pieces)
397       elif key == "ro.build.tags":
398         value = EditTags(value)
399       elif key == "ro.build.display.id":
400         # change, eg, "JWR66N dev-keys" to "JWR66N"
401         value = value.split()
402         if len(value) > 1 and value[-1].endswith("-keys"):
403           value.pop()
404         value = " ".join(value)
405       line = key + "=" + value
406     if line != original_line:
407       print "  replace: ", original_line
408       print "     with: ", line
409     output.append(line)
410   return "\n".join(output) + "\n"
411
412
413 def ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info):
414   try:
415     keylist = input_tf_zip.read("META/otakeys.txt").split()
416   except KeyError:
417     raise common.ExternalError("can't read META/otakeys.txt from input")
418
419   extra_recovery_keys = misc_info.get("extra_recovery_keys", None)
420   if extra_recovery_keys:
421     extra_recovery_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem"
422                            for k in extra_recovery_keys.split()]
423     if extra_recovery_keys:
424       print "extra recovery-only key(s): " + ", ".join(extra_recovery_keys)
425   else:
426     extra_recovery_keys = []
427
428   mapped_keys = []
429   for k in keylist:
430     m = re.match(r"^(.*)\.x509\.pem$", k)
431     if not m:
432       raise common.ExternalError(
433           "can't parse \"%s\" from META/otakeys.txt" % (k,))
434     k = m.group(1)
435     mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem")
436
437   if mapped_keys:
438     print "using:\n   ", "\n   ".join(mapped_keys)
439     print "for OTA package verification"
440   else:
441     devkey = misc_info.get("default_system_dev_certificate",
442                            "build/target/product/security/testkey")
443     mapped_keys.append(
444         OPTIONS.key_map.get(devkey, devkey) + ".x509.pem")
445     print("META/otakeys.txt has no keys; using %s for OTA package"
446           " verification." % (mapped_keys[0],))
447
448   # recovery uses a version of the key that has been slightly
449   # predigested (by DumpPublicKey.java) and put in res/keys.
450   # extra_recovery_keys are used only in recovery.
451
452   p = common.Run(["java", "-jar",
453                   os.path.join(OPTIONS.search_path, "framework", "dumpkey.jar")]
454                  + mapped_keys + extra_recovery_keys,
455                  stdout=subprocess.PIPE)
456   new_recovery_keys, _ = p.communicate()
457   if p.returncode != 0:
458     raise common.ExternalError("failed to run dumpkeys")
459
460   # system_root_image puts the recovery keys at BOOT/RAMDISK.
461   if misc_info.get("system_root_image") == "true":
462     recovery_keys_location = "BOOT/RAMDISK/res/keys"
463   else:
464     recovery_keys_location = "RECOVERY/RAMDISK/res/keys"
465   common.ZipWriteStr(output_tf_zip, recovery_keys_location, new_recovery_keys)
466
467   # SystemUpdateActivity uses the x509.pem version of the keys, but
468   # put into a zipfile system/etc/security/otacerts.zip.
469   # We DO NOT include the extra_recovery_keys (if any) here.
470
471   temp_file = cStringIO.StringIO()
472   certs_zip = zipfile.ZipFile(temp_file, "w")
473   for k in mapped_keys:
474     common.ZipWrite(certs_zip, k)
475   common.ZipClose(certs_zip)
476   common.ZipWriteStr(output_tf_zip, "SYSTEM/etc/security/otacerts.zip",
477                      temp_file.getvalue())
478
479   # For A/B devices, update the payload verification key.
480   if misc_info.get("ab_update") == "true":
481     # Unlike otacerts.zip that may contain multiple keys, we can only specify
482     # ONE payload verification key.
483     if len(mapped_keys) > 1:
484       print("\n  WARNING: Found more than one OTA keys; Using the first one"
485             " as payload verification key.\n\n")
486
487     print "Using %s for payload verification." % (mapped_keys[0],)
488     common.ZipWrite(
489         output_tf_zip,
490         mapped_keys[0],
491         arcname="SYSTEM/etc/update_engine/update-payload-key.pub.pem")
492
493   return new_recovery_keys
494
495 def ReplaceVerityPublicKey(targetfile_zip, filename, key_path):
496   print "Replacing verity public key with %s" % key_path
497   with open(key_path) as f:
498     data = f.read()
499   common.ZipWriteStr(targetfile_zip, filename, data)
500   return data
501
502 def ReplaceVerityPrivateKey(targetfile_input_zip, targetfile_output_zip,
503                             misc_info, key_path):
504   print "Replacing verity private key with %s" % key_path
505   current_key = misc_info["verity_key"]
506   original_misc_info = targetfile_input_zip.read("META/misc_info.txt")
507   new_misc_info = original_misc_info.replace(current_key, key_path)
508   common.ZipWriteStr(targetfile_output_zip, "META/misc_info.txt", new_misc_info)
509   misc_info["verity_key"] = key_path
510
511 def ReplaceVerityKeyId(targetfile_input_zip, targetfile_output_zip, keypath):
512   in_cmdline = targetfile_input_zip.read("BOOT/cmdline")
513   # copy in_cmdline to output_zip if veritykeyid is not present in in_cmdline
514   if "veritykeyid" not in in_cmdline:
515     common.ZipWriteStr(targetfile_output_zip, "BOOT/cmdline", in_cmdline)
516     return in_cmdline
517   out_cmdline = []
518   for param in in_cmdline.split():
519     if "veritykeyid" in param:
520       # extract keyid using openssl command
521       p = common.Run(["openssl", "x509", "-in", keypath, "-text"], stdout=subprocess.PIPE)
522       keyid, stderr = p.communicate()
523       keyid = re.search(r'keyid:([0-9a-fA-F:]*)', keyid).group(1).replace(':', '').lower()
524       print "Replacing verity keyid with %s error=%s" % (keyid, stderr)
525       out_cmdline.append("veritykeyid=id:%s" % (keyid,))
526     else:
527       out_cmdline.append(param)
528
529   out_cmdline = ' '.join(out_cmdline)
530   out_cmdline = out_cmdline.strip()
531   print "out_cmdline %s" % (out_cmdline)
532   common.ZipWriteStr(targetfile_output_zip, "BOOT/cmdline", out_cmdline)
533   return out_cmdline
534
535 def BuildKeyMap(misc_info, key_mapping_options):
536   for s, d in key_mapping_options:
537     if s is None:   # -d option
538       devkey = misc_info.get("default_system_dev_certificate",
539                              "build/target/product/security/testkey")
540       devkeydir = os.path.dirname(devkey)
541
542       OPTIONS.key_map.update({
543           devkeydir + "/testkey":  d + "/releasekey",
544           devkeydir + "/devkey":   d + "/releasekey",
545           devkeydir + "/media":    d + "/media",
546           devkeydir + "/shared":   d + "/shared",
547           devkeydir + "/platform": d + "/platform",
548           })
549     else:
550       OPTIONS.key_map[s] = d
551
552
553 def GetApiLevelAndCodename(input_tf_zip):
554   data = input_tf_zip.read("SYSTEM/build.prop")
555   api_level = None
556   codename = None
557   for line in data.split("\n"):
558     line = line.strip()
559     original_line = line
560     if line and line[0] != '#' and "=" in line:
561       key, value = line.split("=", 1)
562       key = key.strip()
563       if key == "ro.build.version.sdk":
564         api_level = int(value.strip())
565       elif key == "ro.build.version.codename":
566         codename = value.strip()
567
568   if api_level is None:
569     raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
570   if codename is None:
571     raise ValueError("No ro.build.version.codename in SYSTEM/build.prop")
572
573   return (api_level, codename)
574
575
576 def GetCodenameToApiLevelMap(input_tf_zip):
577   data = input_tf_zip.read("SYSTEM/build.prop")
578   api_level = None
579   codenames = None
580   for line in data.split("\n"):
581     line = line.strip()
582     original_line = line
583     if line and line[0] != '#' and "=" in line:
584       key, value = line.split("=", 1)
585       key = key.strip()
586       if key == "ro.build.version.sdk":
587         api_level = int(value.strip())
588       elif key == "ro.build.version.all_codenames":
589         codenames = value.strip().split(",")
590
591   if api_level is None:
592     raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
593   if codenames is None:
594     raise ValueError("No ro.build.version.all_codenames in SYSTEM/build.prop")
595
596   result = dict()
597   for codename in codenames:
598     codename = codename.strip()
599     if len(codename) > 0:
600       result[codename] = api_level
601   return result
602
603
604 def main(argv):
605
606   key_mapping_options = []
607
608   def option_handler(o, a):
609     if o in ("-e", "--extra_apks"):
610       names, key = a.split("=")
611       names = names.split(",")
612       for n in names:
613         OPTIONS.extra_apks[n] = key
614     elif o in ("-d", "--default_key_mappings"):
615       key_mapping_options.append((None, a))
616     elif o in ("-k", "--key_mapping"):
617       key_mapping_options.append(a.split("=", 1))
618     elif o in ("-o", "--replace_ota_keys"):
619       OPTIONS.replace_ota_keys = True
620     elif o in ("-t", "--tag_changes"):
621       new = []
622       for i in a.split(","):
623         i = i.strip()
624         if not i or i[0] not in "-+":
625           raise ValueError("Bad tag change '%s'" % (i,))
626         new.append(i[0] + i[1:].strip())
627       OPTIONS.tag_changes = tuple(new)
628     elif o == "--replace_verity_public_key":
629       OPTIONS.replace_verity_public_key = (True, a)
630     elif o == "--replace_verity_private_key":
631       OPTIONS.replace_verity_private_key = (True, a)
632     elif o == "--replace_verity_keyid":
633       OPTIONS.replace_verity_keyid = (True, a)
634     else:
635       return False
636     return True
637
638   args = common.ParseOptions(argv, __doc__,
639                              extra_opts="e:d:k:ot:",
640                              extra_long_opts=["extra_apks=",
641                                               "default_key_mappings=",
642                                               "key_mapping=",
643                                               "replace_ota_keys",
644                                               "tag_changes=",
645                                               "replace_verity_public_key=",
646                                               "replace_verity_private_key=",
647                                               "replace_verity_keyid="],
648                              extra_option_handler=option_handler)
649
650   if len(args) != 2:
651     common.Usage(__doc__)
652     sys.exit(1)
653
654   input_zip = zipfile.ZipFile(args[0], "r")
655   output_zip = zipfile.ZipFile(args[1], "w")
656
657   misc_info = common.LoadInfoDict(input_zip)
658
659   BuildKeyMap(misc_info, key_mapping_options)
660
661   apk_key_map = GetApkCerts(input_zip)
662   CheckAllApksSigned(input_zip, apk_key_map)
663
664   key_passwords = common.GetKeyPasswords(set(apk_key_map.values()))
665   platform_api_level, platform_codename = GetApiLevelAndCodename(input_zip)
666   codename_to_api_level_map = GetCodenameToApiLevelMap(input_zip)
667   # Android N will be API Level 24, but isn't yet.
668   # TODO: Remove this workaround once Android N is officially API Level 24.
669   if platform_api_level == 23 and platform_codename == "N":
670     platform_api_level = 24
671
672   ProcessTargetFiles(input_zip, output_zip, misc_info,
673                      apk_key_map, key_passwords,
674                      platform_api_level,
675                      codename_to_api_level_map)
676
677   common.ZipClose(input_zip)
678   common.ZipClose(output_zip)
679
680   add_img_to_target_files.AddImagesToTargetFiles(args[1])
681
682   print "done."
683
684
685 if __name__ == '__main__':
686   try:
687     main(sys.argv[1:])
688   except common.ExternalError, e:
689     print
690     print "   ERROR: %s" % (e,)
691     print
692     sys.exit(1)