OSDN Git Service

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