• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"""
18Signs all the APK files in a target-files zipfile, producing a new
19target-files zip.
20
21Usage:  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
83import sys
84
85if sys.hexversion < 0x02070000:
86  print >> sys.stderr, "Python 2.7 or newer is required."
87  sys.exit(1)
88
89import base64
90import cStringIO
91import copy
92import errno
93import os
94import re
95import shutil
96import subprocess
97import tempfile
98import zipfile
99
100import add_img_to_target_files
101import common
102
103OPTIONS = common.OPTIONS
104
105OPTIONS.extra_apks = {}
106OPTIONS.key_map = {}
107OPTIONS.replace_ota_keys = False
108OPTIONS.replace_verity_public_key = False
109OPTIONS.replace_verity_private_key = False
110OPTIONS.replace_verity_keyid = False
111OPTIONS.tag_changes = ("-test-keys", "-dev-keys", "+release-keys")
112
113def 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
129def 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
146def 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
182def 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
353def 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
383def 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
395def 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
437def 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
476  p = common.Run(["java", "-jar",
477                  os.path.join(OPTIONS.search_path, "framework", "dumpkey.jar")]
478                 + mapped_keys + extra_recovery_keys,
479                 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
528def 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
536def 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
546def 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
571def 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
589def 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
612def 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
640def 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
721if __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)
729